1 Resolució de la pràctica 1


1.1 Plantejament del problema

1.1.1 Context

Una de les virtuts dels esports és que sempre hi ha la possibilitat que un rival teòricament inferior en guanyi al que suposadament és millor. Aquesta incapacitat d’endevinar un resultat és una de les claus que fan que els aficionats segueixin enganxats a les seves pantalles.

Tot i això, els humans tenim tendència a intentar predir el futur. Això passa en tots els camps, i el futbol no se n’escapa. Des de ben aviat, els seguidors fan juguesques per a demostrar qui és el que en sap més. Fins i tot, hi ha persones que es dediquen professionalment al camp de les apostes esportives.

Els avenços en els camps de l’estadística i l’aprenentatge automàtic han aportat una nova dimensió al món de les prediccions esportives. Ara, durant els partits s’emmagatzemen una gran quantitat de dades que posteriorment són utilitzats per analistes de dades per a desenvolupar models predictius.

En aquest treball, es proposa estudiar dades de futbol per entendre’l millor i per aconseguir un model que permeti predir tant el guanyador del partit com els gols marcats per cada equip.

1.1.2 Objectius

Com ja s’ha comentat anteriorment, una de les tasques és fer un estudi de les dades per a identificar característiques importants (sobre els jugadors, els partits, etc.) que puguin determinar el resultat d’un partit.

Aquesta descripció és molt àmplia i, per tant, cal concretar-la en petites tasques. Ens centrarem principalment en els equips, els jugadors i els entrenadors. Es pot obtenir dades bàsiques, com quins són els que més partits guanyen, més gols marquen, targetes rebudes. Dades una mica més elaborades com intentar conèixer quins són els jugadors i combinacions de jugadors més influents dins del terreny de joc.

A més a més, també pot ser rellevant visualitzar aquestes dades tenint en compte el context d’aquests partits. És a dir, saber si hi ha diferència entre els equips que juguen de local o visitant, veure si l’hora influeix en el nombre de gols marcats, entendre si les diverses lligues i competicions determinen resultats diferents i observar si existeixen diferències entre cada temporada.

Aquesta feina de visualització permetrà conèixer molt millor les dades i facilitar la creació del model de machine learning. Com s’ha dit en l’anterior secció, aquest model ha de ser capaç de predir el guanyador d’un partit a partir d’unes dades d’entrada. Per tant, aquest seria un model de classificació.

Addicionalment, també es pot desenvolupar un model que sigui capaç de determinar la quantitat de gols d’un partit i la quantitat de gols marcats per cada equip. En conseqüència, aquest seria un model de regressió.

I finalment, es podria fer una anàlisi no supervisada per intentar agrupar els equips per característiques similars.

1.2 Dataset

1.2.1 Descripció del joc de dades

El dataset escollit per a dur a terme aquest treball és European Soccer Database. Aquest joc de dades es troba disponible de forma gratuita a la pàgina web Kaggle. Ha sigut creat especificament per a fer-la servir en projectes d’anàlisi de dades i aprenentatge automàtic.

Conté més de 25.000 partits, més de 10.000 jugadors, 11 lligues de països europeus, dades des de la temporada 2008/09 fins a la 2015/16, atributs dels jugadors i equips obtinguts del videojoc EA Sports FIFA, alineacions, quotes d’apostes i esdeveniments del partit detallats (tipus de gol, possessió, faltes, centrades, targetes, etc.) de més de 10.000 partits.

Tal com s’explica en la seva descripció, és probable que hi manquin alguns camps. Com per exemple, poden faltar alguns jugadors de les alienacions. Caldrà tenir això present quan es treballi amb el joc de dades.

El fitxer original és una base de dades SQLite. Normalment, estem acostumats a treballar directament amb fitxers .csv o .xlsx, però en el camp de la mineria de dades també és comú treballar directament amb bases de dades.

En total hi ha 7 taules. A continuació, es mostra una descripció del contingut d’aquestes:

Country:

Conté informació sobre els països.

  • 2 variables.
  • 11 registres.
Atribut Descripció
id Identificador del pais
name Nom del pais

League:

Conté informació sobre les lligues disponibles.

  • 3 variables.
  • 11 registres.
Atribut Descripció
id Identificador de la lliga
country_id Identificador del pais
name Nom de la lliga

Match:

Conté informació sobre els partits disponibles.

  • 115 variables.
  • 26.000 registres.
Atribut Descripció
id Identificador del partit
country_id Identificador del pais
league_id Identificador de la lliga
season Temporada
stage Jornada
date Data
+team_api_id Identificador de l’equip local o visitant a l’API
+team_goal Nombre de gols de l’equip local o visitant
home_player+ Onze titular de l’equip local
away_player+ Onze titular de l’equip visitant
goal Nombre de gols. Dades mal formatades
shoton Nombre de tirs a porta. Dades mal formatades
shotoff Nombre de tirs fora de porta. Dades mal formatades
foulcommit Nombre de faltes. Dades mal formatades
card Nombre de targetes. Dades mal formatades
cross Nombre de centrades. Dades mal formatades
corner Nombre de córners. Dades mal formatades
possession Dades de possessió. Dades mal formatades
B365+ Bet365 quotes de victòria/empat/derrota
BW+ Bet&Win quotes de victòria/empat/derrota
IW+ Interwetten quotes de victòria/empat/derrota
LB+ Ladbrokes quotes de victòria/empat/derrota
PS+ Pinnacle quotes de victòria/empat/derrota
WH+ William Hill quotes de victòria/empat/derrota
SJ+ Stan James quotes de victòria/empat/derrota
VC+ VC Bet quotes de victòria/empat/derrota
GB+ Game Bookers quotes de victòria/empat/derrota
BS+ Blue Square quotes de victòria/empat/derrota

El símbol suma ‘+’ de la taula s’ha d’interpretar com a múltiples possibles valors. Es representa d’aquesta forma per a facilitar la llegibilitat de la taula.

Player:

Conté informació descriptiva sobre alguns dels jugadors.

  • 7 variables.
  • 11.100 registres.
Atribut Descripció
id Identificador del jugador
player_api_id Identificador de l’API
player_name Nom complet del jugador
player_fifa_api_id Identificador de l’API del FIFA
birthday Data d’aniversari
height Alçada
weight Pes

Player_Attributes:

Conté informació sobre els atributs dels jugadors al videojoc FIFA.

  • 42 variables.
  • 184.000 registres
Atribut Descripció
id Identificador del jugador
player_fifa_api_id Identificador de l’API del FIFA
player_api_id Identificador de l’API
player_name Nom complet del jugador
date Data
overall_rating Qualificació global
potential Potencial
preferred_foot Peu preferit
+work_rate Taxa d’atac/defensa
crossing Qualificació de les centrades
finishing Qualificació definint
heading_accuracy Precisió amb rematades de cap
short_passing Qualificació de les passades curtes
volleys Qualificació de les volees
dribbling Capacitat per regatejar
curve Capacitat per xutar amb efecte
free_kick_accuracy Precisió amb els tirs lliures
long_passing Qualificació de les passades llargues
ball_control Qualificació del control de pilota
acceleration Qualificació de la capacitat d’accelerar
sprint_speed Velocitat en l’esprint
agility Agilitat del jugador
reactions Capacitat de reaccionar
balance Equilibri
shot_power Potència de tir
jumping Capacitat de saltar
stamina Resistència
strength Força
long_shots Capacitat de fer tirs llunyans
aggression Agresivitat
interceptions Capacitat d’interceptar una pilota
positioning Capacitat de situar-se correctament al camp
vision Capacitat de veure els companys
penaltis Qualificació llençant penaltis
marking Capacitat de marcar a un oponent
standing_tackle Capacitat de prendre la pilota dempeus
sliding_tacke Capacitat de prendre la pilota llençant-se al terra
gk_diving Qualificació de l’estirada d’un porter
gk_handing Habilitat del porter amb les mans
gk_kicking Habilitat de xutar d’un porter
gk_positioning Habilitat d’un porter de col·locar-se correctament
gk_reflexer Reflexes d’un porter

Team:

Conté informació molt bàsica sobre els equips.

  • 5 variables.
  • 299 registres.
Atribut Descripció
id Identificador de l’equip
team_api_id Identificador de l’API
team_fifa_api_id Identificador de l’API del FIFA
team_long_name Nom llarg de l’equip
team_short_name Abreviació del nom de l’equip

Team_Attributes:

Conté informació sobre estils de joc dels equips.

  • 25 variables.
  • 1458 registres.
Atribut Descripció
id Identificador de l’equip
team_fifa_api_id Identificador de l’API del FIFA
team_api_id Identificador de l’API
date Data
buildUpPlaySpeed Velocitat de construcció de joc
buildUpPlaySpeedClass Tipus de velocitat de construcció de joc
buildUpPlayDribbling Quantitat de regats en la construcció de joc
buildUpPlayDribblingClass Categories de quantitat de regats
buildUpPlayPassing Quantitat de passades en la construcció de joc
buildUpPlayPassingClass Categories de quantitat de passades
buildUpPlayPositioningClass Categories de posicionament en la construcció de joc
chanceCreationPassing Quantitat de passades
chanceCreationPassingClass Tipus de passades
chanceCreationCrossing Oportunitats creades amb centrades
chanceCreationCrossingClass Tipus de centrades
chanceCreationShooting Quantitat de xuts
chanceCreationShootingClass Tipus de xuts
chanceCreationPositioningClass Tipus d’organització
defencePressure Quantitat de pressió defensiva
defencePressureClass Tipus de pressió defensiva
defenceAggression Agressivitat defensiva
defenceAggressionClass Tipus d’agressivitat defensiva
defenceTeamWidth Amplada defensiva
defenceTeamWidthClass Tipus d’amplada defensiva
defenceDefenderLineClass Tipus de defensa

1.2.2 Justificació de l’elecció

S’ha escollit aquest joc de dades per què dels datasets disponibles sobre futbol, aquest és un dels més complets. Molts d’ells no contenen gaire informació dels equips, partits i jugadors, en canvi, aquest sí. És cert que no conté dades gaire actualitzades, però això no és rellevant.

A més, se li poden aplicar tant algorismes supervisats com no supervisats, conté més de 500 observacions, més de 5 variables numèriques, més de 2 categòriques i més d’1 binària.

Pel que fa a la seva complexitat es pot comentar que té múltiples taules i això farà que no sigui tan fàcil treballar amb ell. A més, les dades no estan gaire polides i hi ha poca documentació sobre el significat de les columnes, ha calgut fer recerca per esbrinar el significat d’algunes de les variables. I finalment, és un arxiu SQLite, i no estem massa acostumats a fer servir aquest tipus de base de dades.

1.3 EDA (Exploratory Data Analysis)

En aquesta secció es fa una anàlisi de les dades per entendre-les. Cal fer una investigació dels registres i atributs per obtenir les seues principals característiques. Els objectius principals d’aquesta fase són extreure’n el coneixement suficient per a saber com manipular les fonts de dades en posteriors estudis.

Abans de res cal aconseguir les dades. Les tenim en un arxiu sqlite, per tant, cal carregar-lo en una base de dades, obtenir les seves taules i guardar-les com a Data Frames per a poder-les fer servir en R.

A continuació, es crida un script que s’encarrega de fer aquesta feina:

# Load the RSQLite Library
if (!require("RSQLite")) install.packages("RSQLite", repos = "http:/cran.us.r-project.org"); library("RSQLite")

# Create connection to the SQLite DB file
mydb <- dbConnect(RSQLite::SQLite(), "data/database.sqlite")

# Retrieve dataframes
df_country           <- dbGetQuery(mydb, "SELECT * FROM Country")
df_league            <- dbGetQuery(mydb, "SELECT * FROM League")
df_match             <- dbGetQuery(mydb, "SELECT * FROM Match")
df_player            <- dbGetQuery(mydb, "SELECT * FROM Player")
df_player_attributes <- dbGetQuery(mydb, "SELECT * FROM Player_Attributes")
df_team              <- dbGetQuery(mydb, "SELECT * FROM Team")
df_team_attributes   <- dbGetQuery(mydb, "SELECT * FROM Team_Attributes")

# Close connection with DB
dbDisconnect(mydb)

1.3.1 Anàlisi preliminar

En aquesta secció es dona una primera ullada a les taules que acabem de carregar i analitzem la seua estructura.

Country i League:

Visualitzem les dades de les taules country i league:

df_country
##       id        name
## 1      1     Belgium
## 2   1729     England
## 3   4769      France
## 4   7809     Germany
## 5  10257       Italy
## 6  13274 Netherlands
## 7  15722      Poland
## 8  17642    Portugal
## 9  19694    Scotland
## 10 21518       Spain
## 11 24558 Switzerland
df_league
##       id country_id                     name
## 1      1          1   Belgium Jupiler League
## 2   1729       1729   England Premier League
## 3   4769       4769           France Ligue 1
## 4   7809       7809    Germany 1. Bundesliga
## 5  10257      10257            Italy Serie A
## 6  13274      13274   Netherlands Eredivisie
## 7  15722      15722       Poland Ekstraklasa
## 8  17642      17642 Portugal Liga ZON Sagres
## 9  19694      19694  Scotland Premier League
## 10 21518      21518          Spain LIGA BBVA
## 11 24558      24558 Switzerland Super League

Com que és una taula auxiliar no conté gaires registres. La seua única finalitat serà la de facilitar la visualització de la resta de taules.

Match:

Aquesta taula és una de les més importants de tot el conjunt de dades. Ens servirà per conèixer els resultats dels partits i les seues principals característiques.

Fem una visualització de la seva estructura:

str(df_match)
## 'data.frame':    25979 obs. of  115 variables:
##  $ id              : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ country_id      : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ league_id       : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ season          : chr  "2008/2009" "2008/2009" "2008/2009" "2008/2009" ...
##  $ stage           : int  1 1 1 1 1 1 1 1 1 10 ...
##  $ date            : chr  "2008-08-17 00:00:00" "2008-08-16 00:00:00" "2008-08-16 00:00:00" "2008-08-17 00:00:00" ...
##  $ match_api_id    : int  492473 492474 492475 492476 492477 492478 492479 492480 492481 492564 ...
##  $ home_team_api_id: int  9987 10000 9984 9991 7947 8203 9999 4049 10001 8342 ...
##  $ away_team_api_id: int  9993 9994 8635 9998 9985 8342 8571 9996 9986 8571 ...
##  $ home_team_goal  : int  1 0 0 5 1 1 2 1 1 4 ...
##  $ away_team_goal  : int  1 0 3 0 3 1 2 2 0 1 ...
##  $ home_player_X1  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X2  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X3  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X4  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X5  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X6  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X7  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X8  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X9  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X10 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_X11 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X1  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X2  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X3  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X4  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X5  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X6  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X7  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X8  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X9  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X10 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_X11 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y1  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y2  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y3  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y4  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y5  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y6  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y7  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y8  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y9  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y10 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_Y11 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y1  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y2  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y3  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y4  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y5  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y6  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y7  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y8  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y9  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y10 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_Y11 : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_1   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_2   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_3   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_4   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_5   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_6   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_7   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_8   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_9   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_10  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ home_player_11  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_1   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_2   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_3   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_4   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_5   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_6   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_7   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_8   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_9   : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_10  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ away_player_11  : int  NA NA NA NA NA NA NA NA NA NA ...
##  $ goal            : chr  NA NA NA NA ...
##  $ shoton          : chr  NA NA NA NA ...
##  $ shotoff         : chr  NA NA NA NA ...
##  $ foulcommit      : chr  NA NA NA NA ...
##  $ card            : chr  NA NA NA NA ...
##  $ cross           : chr  NA NA NA NA ...
##  $ corner          : chr  NA NA NA NA ...
##  $ possession      : chr  NA NA NA NA ...
##  $ B365H           : num  1.73 1.95 2.38 1.44 5 4.75 2.1 3.2 2.25 1.3 ...
##  $ B365D           : num  3.4 3.2 3.3 3.75 3.5 3.4 3.2 3.4 3.25 5.25 ...
##  $ B365A           : num  5 3.6 2.75 7.5 1.65 1.67 3.3 2.2 2.88 9.5 ...
##  $ BWH             : num  1.75 1.8 2.4 1.4 5 4.85 2.05 2.55 2.3 1.25 ...
##  $ BWD             : num  3.35 3.3 3.3 4 3.5 3.4 3.25 3.3 3.25 5 ...
##  $ BWA             : num  4.2 3.95 2.55 6.8 1.6 1.65 3.15 2.4 2.7 10 ...
##  $ IWH             : num  1.85 1.9 2.6 1.4 4 3.7 1.85 2.4 2.1 1.3 ...
##  $ IWD             : num  3.2 3.2 3.1 3.9 3.3 3.2 3.2 3.2 3.1 4.2 ...
##  $ IWA             : num  3.5 3.5 2.3 6 1.7 1.8 3.5 2.4 3 8 ...
##  $ LBH             : num  1.8 1.9 2.5 1.44 4 5 1.83 2.5 2.25 1.25 ...
##  $ LBD             : num  3.3 3.2 3.2 3.6 3.4 3.25 3.3 3.2 3.2 4.5 ...
##  $ LBA             : num  3.75 3.5 2.5 6.5 1.72 1.62 3.6 2.5 2.75 10 ...
##  $ PSH             : num  NA NA NA NA NA NA NA NA NA NA ...
##  $ PSD             : num  NA NA NA NA NA NA NA NA NA NA ...
##   [list output truncated]

Aquestes comandes ens serveixen per saber el tipus de cada variable i fer una primera inspecció al contingut de les variables. També es pot observar que hi ha variables amb valors buits.

Els valors buits (NA) es tractaran més endavant en la secció de Neteja i preparació, però volem indagar una mica més per conèixer quines són les variables a les quals els manquen més valors:

sort(colMeans(is.na(df_match) | df_match == ""), decreasing = TRUE)
##              PSH              PSD              PSA              BSH 
##       0.57011432       0.57011432       0.57011432       0.45490589 
##              BSD              BSA              GBH              GBD 
##       0.45490589       0.45490589       0.45486739       0.45486739 
##              GBA             goal           shoton          shotoff 
##       0.45486739       0.45275030       0.45275030       0.45275030 
##       foulcommit             card            cross           corner 
##       0.45275030       0.45275030       0.45275030       0.45275030 
##       possession              SJH              SJD              SJA 
##       0.45275030       0.34189153       0.34189153       0.34189153 
##              IWH              IWD              IWA              LBH 
##       0.13314600       0.13314600       0.13314600       0.13176027 
##              LBD              LBA              VCH              VCD 
##       0.13176027       0.13176027       0.13129836       0.13129836 
##              VCA              WHH              WHD              WHA 
##       0.13129836       0.13118288       0.13118288       0.13118288 
##              BWH              BWD              BWA            B365H 
##       0.13102891       0.13102891       0.13102891       0.13037453 
##            B365D            B365A  away_player_X11  away_player_Y11 
##       0.13037453       0.13037453       0.07078794       0.07078794 
##   away_player_X9  away_player_X10   away_player_Y9  away_player_Y10 
##       0.07055699       0.07055699       0.07055699       0.07055699 
##   home_player_X3   home_player_X4   home_player_X5   home_player_X6 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##   home_player_X7   home_player_X8   home_player_X9  home_player_X10 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##  home_player_X11   away_player_X1   away_player_X2   away_player_X3 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##   away_player_X4   away_player_X5   away_player_X6   away_player_X7 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##   away_player_X8   home_player_Y3   home_player_Y4   home_player_Y5 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##   home_player_Y6   home_player_Y7   home_player_Y8   home_player_Y9 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##  home_player_Y10  home_player_Y11   away_player_Y1   away_player_Y2 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##   away_player_Y3   away_player_Y4   away_player_Y5   away_player_Y6 
##       0.07051850       0.07051850       0.07051850       0.07051850 
##   away_player_Y7   away_player_Y8   home_player_X1   home_player_X2 
##       0.07051850       0.07051850       0.07009508       0.07009508 
##   home_player_Y1   home_player_Y2   home_player_11   away_player_11 
##       0.07009508       0.07009508       0.05985604       0.05981754 
##   away_player_10   home_player_10    away_player_8    away_player_5 
##       0.05546788       0.05527541       0.05161862       0.05138766 
##    away_player_9    home_player_6    home_player_4    away_player_4 
##       0.05111821       0.05100273       0.05092575       0.05084876 
##    home_player_5    home_player_2    away_player_6    home_player_8 
##       0.05065630       0.05061781       0.05054082       0.05038685 
##    away_player_3    home_player_3    away_player_2    home_player_9 
##       0.04977097       0.04930906       0.04919358       0.04900112 
##    away_player_7    away_player_1    home_player_7    home_player_1 
##       0.04753840       0.04749990       0.04723046       0.04711498 
##               id       country_id        league_id           season 
##       0.00000000       0.00000000       0.00000000       0.00000000 
##            stage             date     match_api_id home_team_api_id 
##       0.00000000       0.00000000       0.00000000       0.00000000 
## away_team_api_id   home_team_goal   away_team_goal 
##       0.00000000       0.00000000       0.00000000

Com podem veure les columnes amb més valors buits són les que tenen relació amb les quotes d’apostes. En principi, no ens preocupa perquè no tenim cap propòsit de predir o entendre les quotes d’apostes. Tot i això, si es canviés d’opinió, es podrien fer servir les columnes B365+, BW+, WH+, VC+, LB+ i IW+.

També es veu com les variables goal, shoton, shotoff, foulcommit, card, cross, corner i possession contenen molts registres buits. A més, aquestes hem vist que estan mal formatades. És una llàstima per què a partir d’aquestes estadístiques dels partits se’n podrien obtenir conclusions interessants. Malauradament, sembla que no les podrem fer servir.

Player i Team:

Ara donem una primera ullada a les taules player i team.

head(df_player)
##   id player_api_id        player_name player_fifa_api_id            birthday
## 1  1        505942 Aaron Appindangoye             218353 1992-02-29 00:00:00
## 2  2        155782    Aaron Cresswell             189615 1989-12-15 00:00:00
## 3  3        162549        Aaron Doran             186170 1991-05-13 00:00:00
## 4  4         30572      Aaron Galindo             140161 1982-05-08 00:00:00
## 5  5         23780       Aaron Hughes              17725 1979-11-08 00:00:00
## 6  6         27316         Aaron Hunt             158138 1986-09-04 00:00:00
##   height weight
## 1 182.88    187
## 2 170.18    146
## 3 170.18    163
## 4 182.88    198
## 5 182.88    154
## 6 182.88    161
str(df_player)
## 'data.frame':    11060 obs. of  7 variables:
##  $ id                : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ player_api_id     : int  505942 155782 162549 30572 23780 27316 564793 30895 528212 101042 ...
##  $ player_name       : chr  "Aaron Appindangoye" "Aaron Cresswell" "Aaron Doran" "Aaron Galindo" ...
##  $ player_fifa_api_id: int  218353 189615 186170 140161 17725 158138 221280 152747 206592 188621 ...
##  $ birthday          : chr  "1992-02-29 00:00:00" "1989-12-15 00:00:00" "1991-05-13 00:00:00" "1982-05-08 00:00:00" ...
##  $ height            : num  183 170 170 183 183 ...
##  $ weight            : int  187 146 163 198 154 161 146 139 181 170 ...
head(df_team)
##   id team_api_id team_fifa_api_id    team_long_name team_short_name
## 1  1        9987              673          KRC Genk             GEN
## 2  2        9993              675      Beerschot AC             BAC
## 3  3       10000            15005  SV Zulte-Waregem             ZUL
## 4  4        9994             2007  Sporting Lokeren             LOK
## 5  5        9984             1750 KSV Cercle Brugge             CEB
## 6  6        8635              229    RSC Anderlecht             AND
str(df_team)
## 'data.frame':    299 obs. of  5 variables:
##  $ id              : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ team_api_id     : int  9987 9993 10000 9994 9984 8635 9991 9998 7947 9985 ...
##  $ team_fifa_api_id: int  673 675 15005 2007 1750 229 674 1747 NA 232 ...
##  $ team_long_name  : chr  "KRC Genk" "Beerschot AC" "SV Zulte-Waregem" "Sporting Lokeren" ...
##  $ team_short_name : chr  "GEN" "BAC" "ZUL" "LOK" ...

Com podem veure, aquestes taules tampoc conten gaire informació sobre els jugadors i els equips. Però es podran fer servir com a taules auxiliars.

Player Attributes:

Aquesta taula també és important, ja que, ens indica els principals atributs dels jugadors de futbol.

Fem una visualització de l’estructura de les dades:

str(df_player_attributes)
## 'data.frame':    183978 obs. of  42 variables:
##  $ id                 : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ player_fifa_api_id : int  218353 218353 218353 218353 218353 189615 189615 189615 189615 189615 ...
##  $ player_api_id      : int  505942 505942 505942 505942 505942 155782 155782 155782 155782 155782 ...
##  $ date               : chr  "2016-02-18 00:00:00" "2015-11-19 00:00:00" "2015-09-21 00:00:00" "2015-03-20 00:00:00" ...
##  $ overall_rating     : int  67 67 62 61 61 74 74 73 73 73 ...
##  $ potential          : int  71 71 66 65 65 76 76 75 75 75 ...
##  $ preferred_foot     : chr  "right" "right" "right" "right" ...
##  $ attacking_work_rate: chr  "medium" "medium" "medium" "medium" ...
##  $ defensive_work_rate: chr  "medium" "medium" "medium" "medium" ...
##  $ crossing           : int  49 49 49 48 48 80 80 79 79 79 ...
##  $ finishing          : int  44 44 44 43 43 53 53 52 51 51 ...
##  $ heading_accuracy   : int  71 71 71 70 70 58 58 57 57 57 ...
##  $ short_passing      : int  61 61 61 60 60 71 71 70 70 70 ...
##  $ volleys            : int  44 44 44 43 43 40 32 29 29 29 ...
##  $ dribbling          : int  51 51 51 50 50 73 73 71 71 71 ...
##  $ curve              : int  45 45 45 44 44 70 70 68 68 68 ...
##  $ free_kick_accuracy : int  39 39 39 38 38 69 69 69 69 69 ...
##  $ long_passing       : int  64 64 64 63 63 68 68 68 68 68 ...
##  $ ball_control       : int  49 49 49 48 48 71 71 70 70 70 ...
##  $ acceleration       : int  60 60 60 60 60 79 79 79 79 79 ...
##  $ sprint_speed       : int  64 64 64 64 64 78 78 78 78 78 ...
##  $ agility            : int  59 59 59 59 59 78 78 78 78 78 ...
##  $ reactions          : int  47 47 47 46 46 67 67 67 67 67 ...
##  $ balance            : int  65 65 65 65 65 90 90 90 90 90 ...
##  $ shot_power         : int  55 55 55 54 54 71 71 71 71 71 ...
##  $ jumping            : int  58 58 58 58 58 85 85 84 84 84 ...
##  $ stamina            : int  54 54 54 54 54 79 79 79 79 79 ...
##  $ strength           : int  76 76 76 76 76 56 56 56 56 56 ...
##  $ long_shots         : int  35 35 35 34 34 62 60 59 58 58 ...
##  $ aggression         : int  71 71 63 62 62 68 68 67 67 67 ...
##  $ interceptions      : int  70 70 41 40 40 67 67 66 66 66 ...
##  $ positioning        : int  45 45 45 44 44 60 60 58 58 58 ...
##  $ vision             : int  54 54 54 53 53 66 66 65 65 65 ...
##  $ penalties          : int  48 48 48 47 47 59 59 59 59 59 ...
##  $ marking            : int  65 65 65 62 62 76 76 76 76 76 ...
##  $ standing_tackle    : int  69 69 66 63 63 75 75 75 75 75 ...
##  $ sliding_tackle     : int  69 69 69 66 66 78 78 78 78 78 ...
##  $ gk_diving          : int  6 6 6 5 5 14 14 14 14 14 ...
##  $ gk_handling        : int  11 11 11 10 10 7 7 7 7 7 ...
##  $ gk_kicking         : int  10 10 10 9 9 9 9 9 9 9 ...
##  $ gk_positioning     : int  8 8 8 7 7 9 9 9 9 9 ...
##  $ gk_reflexes        : int  8 8 8 7 7 12 12 12 12 12 ...

Comprovem també la quantitat de valors buits:

sort(colMeans(is.na(df_player_attributes) | df_player_attributes == ""), decreasing = TRUE)
## attacking_work_rate             volleys               curve             agility 
##         0.017556447         0.014746328         0.014746328         0.014746328 
##             balance             jumping              vision      sliding_tackle 
##         0.014746328         0.014746328         0.014746328         0.014746328 
##      overall_rating           potential      preferred_foot defensive_work_rate 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##            crossing           finishing    heading_accuracy       short_passing 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##           dribbling  free_kick_accuracy        long_passing        ball_control 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##        acceleration        sprint_speed           reactions          shot_power 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##             stamina            strength          long_shots          aggression 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##       interceptions         positioning           penalties             marking 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##     standing_tackle           gk_diving         gk_handling          gk_kicking 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##      gk_positioning         gk_reflexes                  id  player_fifa_api_id 
##         0.004544022         0.004544022         0.000000000         0.000000000 
##       player_api_id                date 
##         0.000000000         0.000000000

Per sort, en aquest cas la gran majoria de registres tenen algun valor.

Team Attributes:

Finalment, fem l’anàlisi preliminar de la taula team_attributes. També és rellevant per què ens indica les principals característiques dels clubs.

Fem una visualització de l’estructura de les dades:

str(df_team_attributes)
## 'data.frame':    1458 obs. of  25 variables:
##  $ id                            : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ team_fifa_api_id              : int  434 434 434 77 77 77 77 77 77 614 ...
##  $ team_api_id                   : int  9930 9930 9930 8485 8485 8485 8485 8485 8485 8576 ...
##  $ date                          : chr  "2010-02-22 00:00:00" "2014-09-19 00:00:00" "2015-09-10 00:00:00" "2010-02-22 00:00:00" ...
##  $ buildUpPlaySpeed              : int  60 52 47 70 47 58 62 58 59 60 ...
##  $ buildUpPlaySpeedClass         : chr  "Balanced" "Balanced" "Balanced" "Fast" ...
##  $ buildUpPlayDribbling          : int  NA 48 41 NA NA NA NA 64 64 NA ...
##  $ buildUpPlayDribblingClass     : chr  "Little" "Normal" "Normal" "Little" ...
##  $ buildUpPlayPassing            : int  50 56 54 70 52 62 45 62 53 40 ...
##  $ buildUpPlayPassingClass       : chr  "Mixed" "Mixed" "Mixed" "Long" ...
##  $ buildUpPlayPositioningClass   : chr  "Organised" "Organised" "Organised" "Organised" ...
##  $ chanceCreationPassing         : int  60 54 54 70 53 45 40 56 51 45 ...
##  $ chanceCreationPassingClass    : chr  "Normal" "Normal" "Normal" "Risky" ...
##  $ chanceCreationCrossing        : int  65 63 63 70 48 70 50 68 72 35 ...
##  $ chanceCreationCrossingClass   : chr  "Normal" "Normal" "Normal" "Lots" ...
##  $ chanceCreationShooting        : int  55 64 64 70 52 55 55 57 63 55 ...
##  $ chanceCreationShootingClass   : chr  "Normal" "Normal" "Normal" "Lots" ...
##  $ chanceCreationPositioningClass: chr  "Organised" "Organised" "Organised" "Organised" ...
##  $ defencePressure               : int  50 47 47 60 47 40 42 41 49 30 ...
##  $ defencePressureClass          : chr  "Medium" "Medium" "Medium" "Medium" ...
##  $ defenceAggression             : int  55 44 44 70 47 40 42 42 45 70 ...
##  $ defenceAggressionClass        : chr  "Press" "Press" "Press" "Double" ...
##  $ defenceTeamWidth              : int  45 54 54 70 52 60 60 60 63 30 ...
##  $ defenceTeamWidthClass         : chr  "Normal" "Normal" "Normal" "Wide" ...
##  $ defenceDefenderLineClass      : chr  "Cover" "Cover" "Cover" "Cover" ...

Veiem que conté molts atributs, però alguns estan duplicats. Algunes variables són valors numèrics i les altres variables categòriques. És molt probable que en la majoria de casos ja en tinguem prou fent servir una de les dos.

Comprovem també la quantitat de valors buits:

sort(colMeans(is.na(df_team_attributes) | df_team_attributes == ""), decreasing = TRUE)
##           buildUpPlayDribbling                             id 
##                      0.6646091                      0.0000000 
##               team_fifa_api_id                    team_api_id 
##                      0.0000000                      0.0000000 
##                           date               buildUpPlaySpeed 
##                      0.0000000                      0.0000000 
##          buildUpPlaySpeedClass      buildUpPlayDribblingClass 
##                      0.0000000                      0.0000000 
##             buildUpPlayPassing        buildUpPlayPassingClass 
##                      0.0000000                      0.0000000 
##    buildUpPlayPositioningClass          chanceCreationPassing 
##                      0.0000000                      0.0000000 
##     chanceCreationPassingClass         chanceCreationCrossing 
##                      0.0000000                      0.0000000 
##    chanceCreationCrossingClass         chanceCreationShooting 
##                      0.0000000                      0.0000000 
##    chanceCreationShootingClass chanceCreationPositioningClass 
##                      0.0000000                      0.0000000 
##                defencePressure           defencePressureClass 
##                      0.0000000                      0.0000000 
##              defenceAggression         defenceAggressionClass 
##                      0.0000000                      0.0000000 
##               defenceTeamWidth          defenceTeamWidthClass 
##                      0.0000000                      0.0000000 
##       defenceDefenderLineClass 
##                      0.0000000

Podem observar que en aquesta taula també tenim un percentatge molt petit de registres buits. Només la variable buildUpPlayDribbling conté valors NA, però la seva variable categòrica buildUpPlayDribblingClass és plena.

1.3.2 Anàlisi univariant

En aquest apartat s’analitzen les variables de les taules una per una. Es vol obtenir estadístiques bàsiques sobre elles i visualitzar-les per a entendre-les més fàcilment.

En aquest cas, només fem servir les taules Match, Player, Player_Attributes i Team_Attributes. Tenen atributs numèrics o categòrics que permeten analitzar-los en profunditat. En canvi, les taules Country, League i Team només contenen noms i identificadors, per la qual cosa no té sentit examinar-ne el contingut.

Primer de tot, s’aconsegueixen unes estadístiques bàsiques de les variables de cada taula. S’utilitza la funció summary(...), aquesta mostra un resum de cada variable de la taula que se li passa per paràmetre. Si la variable és numèrica mostra les següents característiques:

  • Min.: Valor mínim de la sèrie de dades.
  • 1st Qu.: Valor del primer quartil de la sèrie de dades.
  • Median: Valor de la mediana de la sèrie de dades.
  • Mean: Valor de la mitja de la sèrie de dades.
  • 3rd Qu.: Valor del tercer quartil de la sèrie de dades.
  • Max.: Valor màxim de la sèrie de dades.

Cal tenir en compte que si un registre conté valors buits (NA) la funció no els té en compte per a fer els càlculs, però en mostra el nombre total.

Després, es generen uns gràfics que faciliten la comprensió de les dades. Per a les variables numèriques es fa servir el gràfic Box Plot per a veure com les dades estan distribuïdes i si contenen valors extrems (outliers). Per a les variables categòriques es creen histogrames, que mostren el nombre de registres que hi ha per cada categoria de la variable. També es fa servir un Pie Chart per a la variable binària.

Match:

Estadístiques:
summary(df_match)
##        id          country_id      league_id        season         
##  Min.   :    1   Min.   :    1   Min.   :    1   Length:25979      
##  1st Qu.: 6496   1st Qu.: 4769   1st Qu.: 4769   Class :character  
##  Median :12990   Median :10257   Median :10257   Mode  :character  
##  Mean   :12990   Mean   :11739   Mean   :11739                     
##  3rd Qu.:19484   3rd Qu.:17642   3rd Qu.:17642                     
##  Max.   :25979   Max.   :24558   Max.   :24558                     
##                                                                    
##      stage           date            match_api_id     home_team_api_id
##  Min.   : 1.00   Length:25979       Min.   : 483129   Min.   :  1601  
##  1st Qu.: 9.00   Class :character   1st Qu.: 768436   1st Qu.:  8475  
##  Median :18.00   Mode  :character   Median :1147511   Median :  8697  
##  Mean   :18.24                      Mean   :1195429   Mean   :  9984  
##  3rd Qu.:27.00                      3rd Qu.:1709852   3rd Qu.:  9925  
##  Max.   :38.00                      Max.   :2216672   Max.   :274581  
##                                                                       
##  away_team_api_id home_team_goal   away_team_goal  home_player_X1  
##  Min.   :  1601   Min.   : 0.000   Min.   :0.000   Min.   :0.0000  
##  1st Qu.:  8475   1st Qu.: 1.000   1st Qu.:0.000   1st Qu.:1.0000  
##  Median :  8697   Median : 1.000   Median :1.000   Median :1.0000  
##  Mean   :  9984   Mean   : 1.545   Mean   :1.161   Mean   :0.9996  
##  3rd Qu.:  9925   3rd Qu.: 2.000   3rd Qu.:2.000   3rd Qu.:1.0000  
##  Max.   :274581   Max.   :10.000   Max.   :9.000   Max.   :2.0000  
##                                                    NA's   :1821    
##  home_player_X2  home_player_X3  home_player_X4  home_player_X5 
##  Min.   :0.000   Min.   :1.000   Min.   :2.000   Min.   :1.000  
##  1st Qu.:2.000   1st Qu.:4.000   1st Qu.:6.000   1st Qu.:8.000  
##  Median :2.000   Median :4.000   Median :6.000   Median :8.000  
##  Mean   :2.074   Mean   :4.061   Mean   :6.049   Mean   :7.545  
##  3rd Qu.:2.000   3rd Qu.:4.000   3rd Qu.:6.000   3rd Qu.:8.000  
##  Max.   :8.000   Max.   :8.000   Max.   :8.000   Max.   :9.000  
##  NA's   :1821    NA's   :1832    NA's   :1832    NA's   :1832   
##  home_player_X6  home_player_X7 home_player_X8 home_player_X9  home_player_X10
##  Min.   :1.000   Min.   :1.00   Min.   :1.00   Min.   :1.000   Min.   :1.000  
##  1st Qu.:2.000   1st Qu.:4.00   1st Qu.:3.00   1st Qu.:5.000   1st Qu.:4.000  
##  Median :3.000   Median :5.00   Median :6.00   Median :5.000   Median :5.000  
##  Mean   :3.185   Mean   :4.77   Mean   :5.31   Mean   :5.822   Mean   :5.389  
##  3rd Qu.:4.000   3rd Qu.:6.00   3rd Qu.:7.00   3rd Qu.:8.000   3rd Qu.:7.000  
##  Max.   :9.000   Max.   :9.00   Max.   :9.00   Max.   :9.000   Max.   :9.000  
##  NA's   :1832    NA's   :1832   NA's   :1832   NA's   :1832    NA's   :1832   
##  home_player_X11 away_player_X1 away_player_X2  away_player_X3  away_player_X4 
##  Min.   :1.000   Min.   :1      Min.   :1.000   Min.   :2.000   Min.   :1.000  
##  1st Qu.:5.000   1st Qu.:1      1st Qu.:2.000   1st Qu.:4.000   1st Qu.:6.000  
##  Median :6.000   Median :1      Median :2.000   Median :4.000   Median :6.000  
##  Mean   :5.783   Mean   :1      Mean   :2.075   Mean   :4.059   Mean   :6.052  
##  3rd Qu.:6.000   3rd Qu.:1      3rd Qu.:2.000   3rd Qu.:4.000   3rd Qu.:6.000  
##  Max.   :7.000   Max.   :6      Max.   :8.000   Max.   :9.000   Max.   :8.000  
##  NA's   :1832    NA's   :1832   NA's   :1832    NA's   :1832    NA's   :1832   
##  away_player_X5  away_player_X6  away_player_X7  away_player_X8 
##  Min.   :1.000   Min.   :1.000   Min.   :1.000   Min.   :1.000  
##  1st Qu.:8.000   1st Qu.:2.000   1st Qu.:4.000   1st Qu.:3.000  
##  Median :8.000   Median :3.000   Median :5.000   Median :6.000  
##  Mean   :7.526   Mean   :3.195   Mean   :4.743   Mean   :5.294  
##  3rd Qu.:8.000   3rd Qu.:4.000   3rd Qu.:6.000   3rd Qu.:7.000  
##  Max.   :9.000   Max.   :9.000   Max.   :9.000   Max.   :9.000  
##  NA's   :1832    NA's   :1832    NA's   :1832    NA's   :1832   
##  away_player_X9  away_player_X10 away_player_X11 home_player_Y1  
##  Min.   :1.000   Min.   :1.000   Min.   :3.000   Min.   :0.0000  
##  1st Qu.:5.000   1st Qu.:4.000   1st Qu.:5.000   1st Qu.:1.0000  
##  Median :5.000   Median :5.000   Median :6.000   Median :1.0000  
##  Mean   :5.808   Mean   :5.476   Mean   :5.766   Mean   :0.9996  
##  3rd Qu.:8.000   3rd Qu.:7.000   3rd Qu.:6.000   3rd Qu.:1.0000  
##  Max.   :9.000   Max.   :9.000   Max.   :8.000   Max.   :3.0000  
##  NA's   :1833    NA's   :1833    NA's   :1839    NA's   :1821    
##  home_player_Y2  home_player_Y3 home_player_Y4 home_player_Y5  home_player_Y6 
##  Min.   :0.000   Min.   :3      Min.   :3      Min.   :3.000   Min.   :3.000  
##  1st Qu.:3.000   1st Qu.:3      1st Qu.:3      1st Qu.:3.000   1st Qu.:6.000  
##  Median :3.000   Median :3      Median :3      Median :3.000   Median :7.000  
##  Mean   :2.999   Mean   :3      Mean   :3      Mean   :3.237   Mean   :6.477  
##  3rd Qu.:3.000   3rd Qu.:3      3rd Qu.:3      3rd Qu.:3.000   3rd Qu.:7.000  
##  Max.   :3.000   Max.   :5      Max.   :5      Max.   :8.000   Max.   :9.000  
##  NA's   :1821    NA's   :1832   NA's   :1832   NA's   :1832    NA's   :1832   
##  home_player_Y7  home_player_Y8   home_player_Y9   home_player_Y10 
##  Min.   :3.000   Min.   : 3.000   Min.   : 1.000   Min.   : 3.000  
##  1st Qu.:6.000   1st Qu.: 7.000   1st Qu.: 7.000   1st Qu.: 8.000  
##  Median :7.000   Median : 7.000   Median : 8.000   Median :10.000  
##  Mean   :6.672   Mean   : 7.239   Mean   : 8.026   Mean   : 9.219  
##  3rd Qu.:7.000   3rd Qu.: 8.000   3rd Qu.: 8.000   3rd Qu.:10.000  
##  Max.   :9.000   Max.   :10.000   Max.   :10.000   Max.   :11.000  
##  NA's   :1832    NA's   :1832     NA's   :1832     NA's   :1832    
##  home_player_Y11 away_player_Y1 away_player_Y2 away_player_Y3 away_player_Y4
##  Min.   : 1.00   Min.   :1      Min.   :3      Min.   :3      Min.   :3     
##  1st Qu.:10.00   1st Qu.:1      1st Qu.:3      1st Qu.:3      1st Qu.:3     
##  Median :10.00   Median :1      Median :3      Median :3      Median :3     
##  Mean   :10.44   Mean   :1      Mean   :3      Mean   :3      Mean   :3     
##  3rd Qu.:11.00   3rd Qu.:1      3rd Qu.:3      3rd Qu.:3      3rd Qu.:3     
##  Max.   :11.00   Max.   :3      Max.   :3      Max.   :7      Max.   :7     
##  NA's   :1832    NA's   :1832   NA's   :1832   NA's   :1832   NA's   :1832  
##  away_player_Y5  away_player_Y6  away_player_Y7  away_player_Y8  
##  Min.   :3.000   Min.   : 3.00   Min.   : 3.00   Min.   : 3.000  
##  1st Qu.:3.000   1st Qu.: 6.00   1st Qu.: 6.00   1st Qu.: 7.000  
##  Median :3.000   Median : 7.00   Median : 7.00   Median : 7.000  
##  Mean   :3.245   Mean   : 6.47   Mean   : 6.68   Mean   : 7.246  
##  3rd Qu.:3.000   3rd Qu.: 7.00   3rd Qu.: 7.00   3rd Qu.: 8.000  
##  Max.   :9.000   Max.   :10.00   Max.   :10.00   Max.   :10.000  
##  NA's   :1832    NA's   :1832    NA's   :1832    NA's   :1832    
##  away_player_Y9   away_player_Y10  away_player_Y11 home_player_1   
##  Min.   : 5.000   Min.   : 6.000   Min.   : 7.00   Min.   :  2984  
##  1st Qu.: 7.000   1st Qu.: 8.000   1st Qu.:10.00   1st Qu.: 30602  
##  Median : 8.000   Median :10.000   Median :10.00   Median : 38230  
##  Mean   : 8.022   Mean   : 9.161   Mean   :10.46   Mean   : 76638  
##  3rd Qu.: 8.000   3rd Qu.:10.000   3rd Qu.:11.00   3rd Qu.: 96836  
##  Max.   :11.000   Max.   :11.000   Max.   :11.00   Max.   :698273  
##  NA's   :1833     NA's   :1833     NA's   :1839    NA's   :1224    
##  home_player_2    home_player_3    home_player_4    home_player_5   
##  Min.   :  2802   Min.   :  2752   Min.   :  2752   Min.   :  2752  
##  1st Qu.: 32574   1st Qu.: 30602   1st Qu.: 30627   1st Qu.: 33579  
##  Median : 42388   Median : 39731   Median : 41060   Median : 45996  
##  Mean   :106854   Mean   : 91601   Mean   : 94540   Mean   :109528  
##  3rd Qu.:159854   3rd Qu.:128037   3rd Qu.:145561   3rd Qu.:160243  
##  Max.   :748432   Max.   :705484   Max.   :723037   Max.   :733787  
##  NA's   :1315     NA's   :1281     NA's   :1323     NA's   :1316    
##  home_player_6    home_player_7    home_player_8    home_player_9   
##  Min.   :  2625   Min.   :  2625   Min.   :  2625   Min.   :  2625  
##  1st Qu.: 31037   1st Qu.: 30895   1st Qu.: 32751   1st Qu.: 33332  
##  Median : 41467   Median : 41432   Median : 43319   Median : 45605  
##  Mean   :102309   Mean   : 97288   Mean   :107291   Mean   :111132  
##  3rd Qu.:150944   3rd Qu.:141699   3rd Qu.:160243   3rd Qu.:164479  
##  Max.   :750584   Max.   :692984   Max.   :693171   Max.   :730065  
##  NA's   :1325     NA's   :1227     NA's   :1309     NA's   :1273    
##  home_player_10   home_player_11   away_player_1    away_player_2   
##  Min.   :  2625   Min.   :  2802   Min.   :  2796   Min.   :  2790  
##  1st Qu.: 32465   1st Qu.: 32627   1st Qu.: 30622   1st Qu.: 32579  
##  Median : 43296   Median : 42091   Median : 38289   Median : 42388  
##  Mean   :105612   Mean   :103414   Mean   : 76628   Mean   :107615  
##  3rd Qu.:158783   3rd Qu.:161291   3rd Qu.: 96836   3rd Qu.:159882  
##  Max.   :742405   Max.   :726956   Max.   :698273   Max.   :748432  
##  NA's   :1436     NA's   :1555     NA's   :1234     NA's   :1278    
##  away_player_3    away_player_4    away_player_5    away_player_6   
##  Min.   :  2752   Min.   :  2752   Min.   :  2790   Min.   :  2625  
##  1st Qu.: 30464   1st Qu.: 30627   1st Qu.: 33454   1st Qu.: 31037  
##  Median : 39892   Median : 41083   Median : 46212   Median : 41634  
##  Mean   : 91127   Mean   : 95084   Mean   :109801   Mean   :102308  
##  3rd Qu.:121080   3rd Qu.:145561   3rd Qu.:160844   3rd Qu.:151079  
##  Max.   :705484   Max.   :728414   Max.   :746419   Max.   :722766  
##  NA's   :1293     NA's   :1321     NA's   :1335     NA's   :1313    
##  away_player_7    away_player_8    away_player_9    away_player_10  
##  Min.   :  2625   Min.   :  2625   Min.   :  2625   Min.   :  2770  
##  1st Qu.: 30920   1st Qu.: 32863   1st Qu.: 33435   1st Qu.: 32627  
##  Median : 41433   Median : 45816   Median : 45860   Median : 45358  
##  Mean   : 97898   Mean   :109265   Mean   :111087   Mean   :107149  
##  3rd Qu.:144996   3rd Qu.:163612   3rd Qu.:164209   3rd Qu.:161291  
##  Max.   :750435   Max.   :717248   Max.   :722766   Max.   :722766  
##  NA's   :1235     NA's   :1341     NA's   :1328     NA's   :1441    
##  away_player_11       goal              shoton            shotoff         
##  Min.   :  2802   Length:25979       Length:25979       Length:25979      
##  1st Qu.: 32747   Class :character   Class :character   Class :character  
##  Median : 42652   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :104933                                                           
##  3rd Qu.:161660                                                           
##  Max.   :726956                                                           
##  NA's   :1554                                                             
##   foulcommit            card              cross              corner         
##  Length:25979       Length:25979       Length:25979       Length:25979      
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##   possession            B365H            B365D           B365A       
##  Length:25979       Min.   : 1.040   Min.   : 1.40   Min.   : 1.080  
##  Class :character   1st Qu.: 1.670   1st Qu.: 3.30   1st Qu.: 2.500  
##  Mode  :character   Median : 2.100   Median : 3.50   Median : 3.500  
##                     Mean   : 2.629   Mean   : 3.84   Mean   : 4.662  
##                     3rd Qu.: 2.800   3rd Qu.: 4.00   3rd Qu.: 5.250  
##                     Max.   :26.000   Max.   :17.00   Max.   :51.000  
##                     NA's   :3387     NA's   :3387    NA's   :3387    
##       BWH              BWD              BWA              IWH        
##  Min.   : 1.030   Min.   : 1.650   Min.   : 1.100   Min.   : 1.030  
##  1st Qu.: 1.650   1st Qu.: 3.200   1st Qu.: 2.500   1st Qu.: 1.650  
##  Median : 2.100   Median : 3.400   Median : 3.400   Median : 2.100  
##  Mean   : 2.559   Mean   : 3.748   Mean   : 4.397   Mean   : 2.468  
##  3rd Qu.: 2.750   3rd Qu.: 3.800   3rd Qu.: 5.000   3rd Qu.: 2.600  
##  Max.   :34.000   Max.   :19.500   Max.   :51.000   Max.   :20.000  
##  NA's   :3404     NA's   :3404     NA's   :3404     NA's   :3459    
##       IWD              IWA              LBH              LBD        
##  Min.   : 1.500   Min.   : 1.100   Min.   : 1.040   Min.   : 1.400  
##  1st Qu.: 3.200   1st Qu.: 2.500   1st Qu.: 1.670   1st Qu.: 3.200  
##  Median : 3.300   Median : 3.300   Median : 2.100   Median : 3.400  
##  Mean   : 3.609   Mean   : 4.151   Mean   : 2.536   Mean   : 3.712  
##  3rd Qu.: 3.700   3rd Qu.: 4.600   3rd Qu.: 2.700   3rd Qu.: 3.750  
##  Max.   :11.000   Max.   :25.000   Max.   :26.000   Max.   :19.000  
##  NA's   :3459     NA's   :3459     NA's   :3423     NA's   :3423    
##       LBA              PSH              PSD              PSA        
##  Min.   : 1.100   Min.   : 1.040   Min.   : 2.200   Min.   : 1.090  
##  1st Qu.: 2.500   1st Qu.: 1.720   1st Qu.: 3.410   1st Qu.: 2.560  
##  Median : 3.300   Median : 2.200   Median : 3.640   Median : 3.610  
##  Mean   : 4.385   Mean   : 2.816   Mean   : 4.132   Mean   : 4.973  
##  3rd Qu.: 5.000   3rd Qu.: 2.980   3rd Qu.: 4.230   3rd Qu.: 5.410  
##  Max.   :51.000   Max.   :36.000   Max.   :29.000   Max.   :47.500  
##  NA's   :3423     NA's   :14811    NA's   :14811    NA's   :14811   
##       WHH              WHD              WHA              SJH        
##  Min.   : 1.020   Min.   : 1.020   Min.   : 1.080   Min.   : 1.040  
##  1st Qu.: 1.670   1st Qu.: 3.200   1st Qu.: 2.500   1st Qu.: 1.670  
##  Median : 2.150   Median : 3.300   Median : 3.400   Median : 2.100  
##  Mean   : 2.579   Mean   : 3.665   Mean   : 4.483   Mean   : 2.566  
##  3rd Qu.: 2.750   3rd Qu.: 3.750   3rd Qu.: 5.000   3rd Qu.: 2.750  
##  Max.   :26.000   Max.   :17.000   Max.   :51.000   Max.   :23.000  
##  NA's   :3408     NA's   :3408     NA's   :3408     NA's   :8882    
##       SJD              SJA              VCH              VCD        
##  Min.   : 1.400   Min.   : 1.100   Min.   : 1.030   Min.   : 1.620  
##  1st Qu.: 3.250   1st Qu.: 2.500   1st Qu.: 1.700   1st Qu.: 3.300  
##  Median : 3.400   Median : 3.500   Median : 2.150   Median : 3.500  
##  Mean   : 3.756   Mean   : 4.622   Mean   : 2.668   Mean   : 3.899  
##  3rd Qu.: 3.800   3rd Qu.: 5.250   3rd Qu.: 2.800   3rd Qu.: 4.000  
##  Max.   :15.000   Max.   :41.000   Max.   :36.000   Max.   :26.000  
##  NA's   :8882     NA's   :8882     NA's   :3411     NA's   :3411    
##       VCA             GBH              GBD              GBA        
##  Min.   : 1.08   Min.   : 1.050   Min.   : 1.450   Min.   : 1.120  
##  1st Qu.: 2.55   1st Qu.: 1.670   1st Qu.: 3.200   1st Qu.: 2.500  
##  Median : 3.50   Median : 2.100   Median : 3.300   Median : 3.400  
##  Mean   : 4.84   Mean   : 2.499   Mean   : 3.648   Mean   : 4.353  
##  3rd Qu.: 5.40   3rd Qu.: 2.650   3rd Qu.: 3.750   3rd Qu.: 5.000  
##  Max.   :67.00   Max.   :21.000   Max.   :11.000   Max.   :34.000  
##  NA's   :3411    NA's   :11817    NA's   :11817    NA's   :11817   
##       BSH              BSD              BSA        
##  Min.   : 1.040   Min.   : 1.330   Min.   : 1.120  
##  1st Qu.: 1.670   1st Qu.: 3.250   1st Qu.: 2.500  
##  Median : 2.100   Median : 3.400   Median : 3.400  
##  Mean   : 2.498   Mean   : 3.661   Mean   : 4.406  
##  3rd Qu.: 2.620   3rd Qu.: 3.750   3rd Qu.: 5.000  
##  Max.   :17.000   Max.   :13.000   Max.   :34.000  
##  NA's   :11818    NA's   :11818    NA's   :11818
Visualitzacions:
  • Boxplot

Aquesta taula té múltiples variables numèriques, però moltes d’elles només contenen identificadors, per tant, no té sentit estudiar la seua distribució.

També hi ha moltes variables relatives a les apostes. Com ja s’ha comentat anteriorment, no es pretén utilitzar-les en cap anàlisi i per això no se’n visualitza el seu Box Plot.

Les variables que resten són home_team_goal i away_team_goal. Obtenim el seu Box Plot, ja que, segurament seran molt importants de cara al nostre estudi.

box_plot_attr <- c("home_team_goal",
                    "away_team_goal")
df_match_aux <- df_match[, which(names(df_match) %in% box_plot_attr)]
boxplot(df_match_aux)

Podem veure que la mediana de totes dos columnes és molt semblant i està pel voltant dels 1,5 gols. Malgrat ser semblants, els gols que marca l’equip visitant no estan tan concentrats i el seu primer quartil es troba a 0, mentre que els gols que marca l’equip local tenen el primer quartil a 1,5 gols i estan molt més concentrats. A causa d’això, també apareixen més valors atípics.

  • Histograma

Ara ha arribat el moment de mostrar la distribució de les variables season i stage amb un histograma. És cert que la variable stage és numèrica i es podria mostrar amb una Box Plot. Però creiem més oportú visualitzar-ho així per què, en realitat, aquests números són els identificadors de cada jornada i les jornades d’una competició són variables categòriques.

Carreguem les llibreries necessàries per a Generació del els gràfics:

if (!require("ggplot2")) install.packages("ggplot2"); library("ggplot2")
if (!require("Rmisc")) install.packages("Rmisc"); library("Rmisc")
if (!require("dplyr")) install.packages("dplyr"); library("dplyr")
if (!require("xfun")) install.packages("xfun"); library("xfun")

Generem l’histograma de la columna season:

ggplot(df_match, aes_string(x = "season")) +
  geom_histogram(stat = "count")

En el gràfic es veu que més o menys cada temporada conté el mateix nombre de registres, l’única que en té relativament pocs és la temporada 2013/2014. En principi, va ser una temporada normal en què es van jugar els mateixos partits que en la resta, així que deduïm que es deu a un error a l’hora de recollir les dades. No hauria de suposar un problema de cara a fer les nostres anàlisis, però caldrà tenir-ho en compte.

Generem l’histograma de la columna stage:

ggplot(df_match, aes_string(x = "stage")) +
  geom_histogram(stat = "count")

Veiem com el nombre de registres per jornada disminueix cap al final de la competició. En certs casos és normal que hi hagi menys partits en algunes jornades perquè en les diverses lligues que tenim, no totes tenen el mateix nombre de jornades.

Hem fet una recerca per corroborar si la manca de registres es deu exclusivament a aquest factor:

  • Belgium Jupiler League: 34 jornades.
  • England Premier League: 38 jornades.
  • France Ligue 1: 38 jornades.
  • Germany 1. Bundesliga: 34 jornades.
  • Italy Serie A: 38 jornades.
  • Netherlands Eredivisie: 34 jornades.
  • Poland Ekstraklasa: 34 jornades.
  • Portugal Liga ZON Sagres: 34 jornades.
  • Scotland Premier League: 38 jornades.
  • Spain LIGA BBVA: 38 jornades.
  • Switzerland Super League: 36 jornades.

Totes les lligues tenen almenys 34 jornades, per tant, és estrany que hi hagi menys partits entre les jornades 31 i 34. Suposem que és un error en recollir les dades.

Player:

Estadístiques:
summary(df_player)
##        id        player_api_id    player_name        player_fifa_api_id
##  Min.   :    1   Min.   :  2625   Length:11060       Min.   :     2    
##  1st Qu.: 2768   1st Qu.: 35556   Class :character   1st Qu.:151890    
##  Median : 5536   Median : 96620   Mode  :character   Median :184671    
##  Mean   : 5538   Mean   :156582                      Mean   :165665    
##  3rd Qu.: 8306   3rd Qu.:212470                      3rd Qu.:203883    
##  Max.   :11075   Max.   :750584                      Max.   :234141    
##    birthday             height          weight     
##  Length:11060       Min.   :157.5   Min.   :117.0  
##  Class :character   1st Qu.:177.8   1st Qu.:159.0  
##  Mode  :character   Median :182.9   Median :168.0  
##                     Mean   :181.9   Mean   :168.4  
##                     3rd Qu.:185.4   3rd Qu.:179.0  
##                     Max.   :208.3   Max.   :243.0
Visualitzacions:
  • Boxplot Igual que amb la taula Match, la taula Player té poques variables numèriques per a visualitzar.

Tot i això, podem fer les gràfiques Box Plot de les variables height i weigth:

box_plot_attr <- c("height",
                    "weight")
df_player_aux <- df_player[, which(names(df_player) %in% box_plot_attr)]
boxplot(df_player_aux)

El primer que podem veure és que la variable height està expressada en centímetres i la variable weight està expressada en pounds. Ens podem plantejar convertir el pes a quilograms per entendre-ho millor.

Això vol dir que l’alçada mitjana els jugadors és de 181 cm i que la mitjana de pes és de 168 pounds, que equival a 76 kg. Veiem que l’alçada està molt concentrada i, en canvi, el pes presenta molta variància.

  • Histograma

Aquesta taula també ens dona informació sobre la data de naixement dels jugadors. En principi, no és gaire interessant conèixer la distribució dels dies en què han nascut els jugadors. Però podem veure en quins anys o mesos han nascut més jugadors.

df_player$birth_year <- format(as.Date(df_player$birthday, format = "%Y-%m-%d"), "%Y")

ggplot(df_player, aes_string(x = "birth_year")) +
  geom_histogram(stat = "count")

Podem veure que segueix una distribució normal lleugerament desplaçada cap a la dreta. L’any en què han nascut més jugadors és el 1988. Aquest joc de dades es va fer l’any 2016, això vol dir que l’edat més repetida era 28 anys. També veiem que els jugadors més joves tenen 17 anys i els més vells en tenen 49. Aquesta diferència d’edat és molt alta, però totalment normal.

Ara creem una nova variable birth_month amb el mes de naixement de cada jugador, i en mostrem el seu histograma:

df_player$birth_month <- format(as.Date(df_player$birthday, format = "%Y-%m-%d"), "%m")

ggplot(df_player, aes_string(x = "birth_month")) +
  geom_histogram(stat = "count")

Es pot veure com clarament hi ha menys jugadors a mesura que més tard neixen. Això sembla lògic, per què els que han nascut a principis d’any tenen més oportunitats de seguir progressant, ja que, s’enfronten a nens més joves.

Fem el mateix amb una nova variable birth_day que conté el dia en què han nascut:

df_player$birth_day <- format(as.Date(df_player$birthday, format = "%Y-%m-%d"), "%d")

ggplot(df_player, aes_string(x = "birth_day")) +
  geom_histogram(stat = "count")

Podem veure com el dia 1 és quan neixen més futbolistes i com el 31 és el dia que en neixen menys. Pel que fa a la resta de dies hi ha alguna diferència, però no és remarcable. Ens podem imaginar que el dia 31 és el que menys dies neixen per què n’hi ha menys durant l’any. Però no sabem per què el dia 1 és el més repetit.

Player Attributes:

Aquesta taula té moltes variables interessants per estudiar. La majoria d’elles són numèriques, i les visualitzem amb gràfiques Box Plot. Com que n’hi ha moltes, les agrupem per similitud. Les variables categòriques les mostrem amb un histograma i la variable binària s’estudia amb una Pie Chart.

Estadístiques:
summary(df_player_attributes)
##        id         player_fifa_api_id player_api_id        date          
##  Min.   :     1   Min.   :     2     Min.   :  2625   Length:183978     
##  1st Qu.: 45995   1st Qu.:155798     1st Qu.: 34763   Class :character  
##  Median : 91990   Median :183488     Median : 77741   Mode  :character  
##  Mean   : 91990   Mean   :165672     Mean   :135901                     
##  3rd Qu.:137984   3rd Qu.:199848     3rd Qu.:191080                     
##  Max.   :183978   Max.   :234141     Max.   :750584                     
##                                                                         
##  overall_rating   potential     preferred_foot     attacking_work_rate
##  Min.   :33.0   Min.   :39.00   Length:183978      Length:183978      
##  1st Qu.:64.0   1st Qu.:69.00   Class :character   Class :character   
##  Median :69.0   Median :74.00   Mode  :character   Mode  :character   
##  Mean   :68.6   Mean   :73.46                                         
##  3rd Qu.:73.0   3rd Qu.:78.00                                         
##  Max.   :94.0   Max.   :97.00                                         
##  NA's   :836    NA's   :836                                           
##  defensive_work_rate    crossing       finishing     heading_accuracy
##  Length:183978       Min.   : 1.00   Min.   : 1.00   Min.   : 1.00   
##  Class :character    1st Qu.:45.00   1st Qu.:34.00   1st Qu.:49.00   
##  Mode  :character    Median :59.00   Median :53.00   Median :60.00   
##                      Mean   :55.09   Mean   :49.92   Mean   :57.27   
##                      3rd Qu.:68.00   3rd Qu.:65.00   3rd Qu.:68.00   
##                      Max.   :95.00   Max.   :97.00   Max.   :98.00   
##                      NA's   :836     NA's   :836     NA's   :836     
##  short_passing      volleys        dribbling         curve      
##  Min.   : 3.00   Min.   : 1.00   Min.   : 1.00   Min.   : 2.00  
##  1st Qu.:57.00   1st Qu.:35.00   1st Qu.:52.00   1st Qu.:41.00  
##  Median :65.00   Median :52.00   Median :64.00   Median :56.00  
##  Mean   :62.43   Mean   :49.47   Mean   :59.18   Mean   :52.97  
##  3rd Qu.:72.00   3rd Qu.:64.00   3rd Qu.:72.00   3rd Qu.:67.00  
##  Max.   :97.00   Max.   :93.00   Max.   :97.00   Max.   :94.00  
##  NA's   :836     NA's   :2713    NA's   :836     NA's   :2713   
##  free_kick_accuracy  long_passing    ball_control    acceleration  
##  Min.   : 1.00      Min.   : 3.00   Min.   : 5.00   Min.   :10.00  
##  1st Qu.:36.00      1st Qu.:49.00   1st Qu.:58.00   1st Qu.:61.00  
##  Median :50.00      Median :59.00   Median :67.00   Median :69.00  
##  Mean   :49.38      Mean   :57.07   Mean   :63.39   Mean   :67.66  
##  3rd Qu.:63.00      3rd Qu.:67.00   3rd Qu.:73.00   3rd Qu.:77.00  
##  Max.   :97.00      Max.   :97.00   Max.   :97.00   Max.   :97.00  
##  NA's   :836        NA's   :836     NA's   :836     NA's   :836    
##   sprint_speed      agility        reactions       balance        shot_power   
##  Min.   :12.00   Min.   :11.00   Min.   :17.0   Min.   :12.00   Min.   : 2.00  
##  1st Qu.:62.00   1st Qu.:58.00   1st Qu.:61.0   1st Qu.:58.00   1st Qu.:54.00  
##  Median :69.00   Median :68.00   Median :67.0   Median :67.00   Median :65.00  
##  Mean   :68.05   Mean   :65.97   Mean   :66.1   Mean   :65.19   Mean   :61.81  
##  3rd Qu.:77.00   3rd Qu.:75.00   3rd Qu.:72.0   3rd Qu.:74.00   3rd Qu.:73.00  
##  Max.   :97.00   Max.   :96.00   Max.   :96.0   Max.   :96.00   Max.   :97.00  
##  NA's   :836     NA's   :2713    NA's   :836    NA's   :2713    NA's   :836    
##     jumping         stamina         strength       long_shots   
##  Min.   :14.00   Min.   :10.00   Min.   :10.00   Min.   : 1.00  
##  1st Qu.:60.00   1st Qu.:61.00   1st Qu.:60.00   1st Qu.:41.00  
##  Median :68.00   Median :69.00   Median :69.00   Median :58.00  
##  Mean   :66.97   Mean   :67.04   Mean   :67.42   Mean   :53.34  
##  3rd Qu.:74.00   3rd Qu.:76.00   3rd Qu.:76.00   3rd Qu.:67.00  
##  Max.   :96.00   Max.   :96.00   Max.   :96.00   Max.   :96.00  
##  NA's   :2713    NA's   :836     NA's   :836     NA's   :836    
##    aggression    interceptions    positioning        vision        penalties  
##  Min.   : 6.00   Min.   : 1.00   Min.   : 2.00   Min.   : 1.00   Min.   : 2   
##  1st Qu.:51.00   1st Qu.:34.00   1st Qu.:45.00   1st Qu.:49.00   1st Qu.:45   
##  Median :64.00   Median :57.00   Median :60.00   Median :60.00   Median :57   
##  Mean   :60.95   Mean   :52.01   Mean   :55.79   Mean   :57.87   Mean   :55   
##  3rd Qu.:73.00   3rd Qu.:68.00   3rd Qu.:69.00   3rd Qu.:69.00   3rd Qu.:67   
##  Max.   :97.00   Max.   :96.00   Max.   :96.00   Max.   :97.00   Max.   :96   
##  NA's   :836     NA's   :836     NA's   :836     NA's   :2713    NA's   :836  
##     marking      standing_tackle sliding_tackle   gk_diving     gk_handling   
##  Min.   : 1.00   Min.   : 1.00   Min.   : 2     Min.   : 1.0   Min.   : 1.00  
##  1st Qu.:25.00   1st Qu.:29.00   1st Qu.:25     1st Qu.: 7.0   1st Qu.: 8.00  
##  Median :50.00   Median :56.00   Median :53     Median :10.0   Median :11.00  
##  Mean   :46.77   Mean   :50.35   Mean   :48     Mean   :14.7   Mean   :16.06  
##  3rd Qu.:66.00   3rd Qu.:69.00   3rd Qu.:67     3rd Qu.:13.0   3rd Qu.:15.00  
##  Max.   :96.00   Max.   :95.00   Max.   :95     Max.   :94.0   Max.   :93.00  
##  NA's   :836     NA's   :836     NA's   :2713   NA's   :836    NA's   :836    
##    gk_kicking  gk_positioning   gk_reflexes   
##  Min.   : 1    Min.   : 1.00   Min.   : 1.00  
##  1st Qu.: 8    1st Qu.: 8.00   1st Qu.: 8.00  
##  Median :12    Median :11.00   Median :11.00  
##  Mean   :21    Mean   :16.13   Mean   :16.44  
##  3rd Qu.:15    3rd Qu.:15.00   3rd Qu.:15.00  
##  Max.   :97    Max.   :96.00   Max.   :96.00  
##  NA's   :836   NA's   :836     NA's   :836
Visualitzacions:
  • Qualificació del jugador Boxplot

Primer de tot, volem analitzar les variables relacionades amb la qualitat general d’un jugador. Aquestes són overall_rating i potential.

Mostrem la seua gràfica Box Plot:

box_plot_attr <- c("overall_rating", "potential")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Veiem que són molt similars, però en general el valor del potencial dels jugadors és més alt que la seua valoració actual.

  • Xutar Boxplot

Ara estudiem les variables que tenen a veure amb la capacitat de xutar la pilota.

A continuació, es mostren les variables finishing, heading_accuracy, volleys, shot_power i long_shots:

box_plot_attr <- c("finishing",
                    "heading_accuracy",
                    "volleys",
                    "shot_power",
                    "long_shots")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Es pot veure que finishing, volleys i long_shots tenen una variabilitat molt elevada. En canvi, heading_accuracy i shot_power mostren uns valors molt més concentrats. A més, aquestes tenen un conjunt de mostres que es poden considerar outliers, però les altres no.

  • Pilota aturada Boxplot

També volem analitzar els atributs que tenen a veure amb la pilota aturada. Aquests són curve, free_kick_accuracy i penalties.

A continuació mostrem el seu Box Plot:

box_plot_attr <- c("curve",
                    "penalties",
                    "free_kick_accuracy")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Podem veure que es generen uns gràfics molt similars. La principal diferència és que en general els jugadors tenen menys precisió llençant faltes que penals. També es veu com l’atribut penalties té valors extrems per sota, per la qual cosa podem deduir que és més complicat no saber llençar un penal que un tir lliure.

  • Passada Boxplot

Ara visualitzem les variables que tenen a veure amb saber realitzar una passada. Aquestes són crossing, short_passing, long_passing i vision.

Aquest és el seu Box Plot:

box_plot_attr <- c("crossing",
                    "short_passing",
                    "long_passing",
                    "vision")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Veiem que la que té més variabilitat és la capacitat de fer una centrada, això ens indica que no és una habilitat que dominin tots els jugadors, segurament canvia molt segons la posició on juguin. La passada en curt sembla que és una qualitat que més jugadors dominen, ja que té un valor mitjà més elevat que les altres, els valors estan més concentrats i té molts outliers per sota.

  • Habilitats diverses Boxplot

En aquest apartat s’hi agrupen certs atributs dels jugadors que no encaixen en un grup concret. Malgrat això, encara guarden alguna relació entre ells.

Aquí es mostra la gràfica de dribbling, ball_control, agility, reactions, balance i jumping:

box_plot_attr <- c("dribbling",
                    "reactions",
                    "ball_control",
                    "agility",
                    "balance",
                    "jumping")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Veiem que en general aquestes tenen uns valors més agrupats que la resta de variables de la taula. També tenen uns valors mitjans lleugerament superiors a les que hem analitzat fins ara. També es veu com, a part de dribbling i ball control la resta tenen uns valors mínims elevats. Llavors, podem deduir que és bastant complicat que els jugadors no siguin àgils, no reaccionin bé, no tinguin un bon equilibri o que no saltin gaire.

  • Velocitat i força Boxplot

Aquí estudiem les variables que tenen a veure amb la força, la velocitat i la resistència. Aquestes són acceleration, sprint_speed, stamina i strength.

A continuació es mostra la gràfica Box Plot:

box_plot_attr <- c("acceleration",
                    "sprint_speed",
                    "stamina",
                    "strength")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Igual que en el cas anterior, també tenen una variabilitat més baixa i uns valors mitjans més alts. Però els seus mínims són més baixos.

  • Defensa Boxplot

Ara analitzem les qualitats que tenen relació amb defensar. Aquestes són aggression, interceptions, positioning, marking, standing_tacke i sliding_tacke.

Aquestes són les seues gràfiques Box Plot:

box_plot_attr <- c("aggression",
                    "positioning",
                    "interceptions",
                    "marking",
                    "standing_tackle",
                    "sliding_tackle")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Podem observar una variabilitat molt més alta, sobretot amb la capacitat d’interceptar una pilota, la capacitat de marcar a un oponent, la de robar una pilota i la de realitzar una segada. Això segurament es deu fet que són característiques purament defensives, i per tant, els davanters i alguns migcampistes no tindran aquestes habilitats.

En canvi, l’agressivitat i el posicionament no són purament defensives perquè un davanter pot necessitar ser agressiu a l’hora de lluitar una pilota i també li cal posicionar-se correctament al camp. Segurament, per això, veiem menys variabilitat.

  • Porter Boxplot

Per acabar l’anàlisi de les variables numèriques ho fem amb les que tenen a veure amb els porters. Aquestes variables són gk_diving, gk_handling, gk_kicking, gk_positioning i gk_reflexes.

Mostrem la gràfica Box Plot:

box_plot_attr <- c("gk_diving",
                    "gk_handling",
                    "gk_kicking",
                    "gk_positioning",
                    "gk_reflexes")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% box_plot_attr)]
boxplot(df_player_attributes_aux)

Podem veure uns valors mitjans molt baixos, una variabilitat molt baixa i molts valors extrems per dalt. Això, el que ens diu és que tots els jugadors (porters i jugadors de camp) tenen aquests atributs assignats, per tant, els jugadors de camp tenen una molt mala puntuació en aquests atributs. Això fa que es distorsioni la gràfica.

Estaria bé poder filtrar per tipus de jugador, però no tenim aquesta variable. El que es pot fer és deduir que els jugadors amb valors elevats en aquestes variables són porters i la resta no. És possible que es pugui cometre algun error, però no hauria de ser rellevant.

  • Preferred Foot Pie Chart

La variable preferred foot és una variable binària que ens indica la preferència de cada jugador en utilitzar la cama esquerra o la dreta.

Fem servir una gràfica circular per a veure quina és la preferida dels jugadors:

if (!require("dplyr")) install.packages("dplyr", repos = "http:/cran.us.r-project.org"); library("dplyr")

labels <- c("Esquerra", "Dreta")
df_preferred_foot <- df_player_attributes %>%
                      group_by(preferred_foot) %>%
                      count()
pie(df_preferred_foot$n, labels = labels)

Podem veure que tres quarts dels jugadors són dretans. Segons la pàgina web LEFTYFRETZ al món només el 12% de les persones és esquerrana, això voldria dir que en els futbolistes hi ha una proporció més alta d’esquerrans que en general. Un dels motius podria ser que a Europa la taxa d’esquerrans és més alta que en la resta del món i un altre que és més fàcil que algú sigui esquerrà si és un home. Malgrat tot, aquesta xifra sorprèn per què els motius citats no acaben d’explicar que n’hi hagi tants.

  • Histograma

Per acabar l’anàlisi de la taula Player_Attributes estudiem la distribució dels seus atributs categòrics amb un histograma.

A continuació, es mostren els histogrames de les variables attacking_work_rate i defensive_work_rate:

hist_list <- list()

dist_attr <- c("attacking_work_rate", "defensive_work_rate")
df_player_attributes_aux <- df_player_attributes[, which(names(df_player_attributes) %in% dist_attr)]

for(i in seq_len(ncol(df_player_attributes_aux))) {
  col <- names(df_player_attributes_aux)[i]
  ggp <- ggplot(df_player_attributes_aux, aes_string(x = col)) +
    geom_histogram(stat = "count") 
      hist_list[[i]] <- ggp  # afegim cada plot a la llista buida
}
multiplot(plotlist = hist_list, cols = 1)

Podem veure que en tots dos casos hi ha categories estranyes. Les que tenen més valors són high, low i medium. La resta semblen errors, ja que no corresponen amb aquesta lògica ni tenen gaires valors. Per tant, caldrà tenir-ho en compte a l’hora de netejar les dades.

Team Attributes:

Estadístiques:
summary(df_team_attributes)
##        id         team_fifa_api_id  team_api_id         date          
##  Min.   :   1.0   Min.   :     1   Min.   :  1601   Length:1458       
##  1st Qu.: 365.2   1st Qu.:   110   1st Qu.:  8458   Class :character  
##  Median : 729.5   Median :   485   Median :  8674   Mode  :character  
##  Mean   : 729.5   Mean   : 17707   Mean   :  9996                     
##  3rd Qu.:1093.8   3rd Qu.:  1900   3rd Qu.:  9904                     
##  Max.   :1458.0   Max.   :112513   Max.   :274581                     
##                                                                       
##  buildUpPlaySpeed buildUpPlaySpeedClass buildUpPlayDribbling
##  Min.   :20.00    Length:1458           Min.   :24.00       
##  1st Qu.:45.00    Class :character      1st Qu.:42.00       
##  Median :52.00    Mode  :character      Median :49.00       
##  Mean   :52.46                          Mean   :48.61       
##  3rd Qu.:62.00                          3rd Qu.:55.00       
##  Max.   :80.00                          Max.   :77.00       
##                                         NA's   :969         
##  buildUpPlayDribblingClass buildUpPlayPassing buildUpPlayPassingClass
##  Length:1458               Min.   :20.00      Length:1458            
##  Class :character          1st Qu.:40.00      Class :character       
##  Mode  :character          Median :50.00      Mode  :character       
##                            Mean   :48.49                             
##                            3rd Qu.:55.00                             
##                            Max.   :80.00                             
##                                                                      
##  buildUpPlayPositioningClass chanceCreationPassing chanceCreationPassingClass
##  Length:1458                 Min.   :21.00         Length:1458               
##  Class :character            1st Qu.:46.00         Class :character          
##  Mode  :character            Median :52.00         Mode  :character          
##                              Mean   :52.17                                   
##                              3rd Qu.:59.00                                   
##                              Max.   :80.00                                   
##                                                                              
##  chanceCreationCrossing chanceCreationCrossingClass chanceCreationShooting
##  Min.   :20.00          Length:1458                 Min.   :22.00         
##  1st Qu.:47.00          Class :character            1st Qu.:48.00         
##  Median :53.00          Mode  :character            Median :53.00         
##  Mean   :53.73                                      Mean   :53.97         
##  3rd Qu.:62.00                                      3rd Qu.:61.00         
##  Max.   :80.00                                      Max.   :80.00         
##                                                                           
##  chanceCreationShootingClass chanceCreationPositioningClass defencePressure
##  Length:1458                 Length:1458                    Min.   :23.00  
##  Class :character            Class :character               1st Qu.:39.00  
##  Mode  :character            Mode  :character               Median :45.00  
##                                                             Mean   :46.02  
##                                                             3rd Qu.:51.00  
##                                                             Max.   :72.00  
##                                                                            
##  defencePressureClass defenceAggression defenceAggressionClass defenceTeamWidth
##  Length:1458          Min.   :24.00     Length:1458            Min.   :29.00   
##  Class :character     1st Qu.:44.00     Class :character       1st Qu.:47.00   
##  Mode  :character     Median :48.00     Mode  :character       Median :52.00   
##                       Mean   :49.25                            Mean   :52.19   
##                       3rd Qu.:55.00                            3rd Qu.:58.00   
##                       Max.   :72.00                            Max.   :73.00   
##                                                                                
##  defenceTeamWidthClass defenceDefenderLineClass
##  Length:1458           Length:1458             
##  Class :character      Class :character        
##  Mode  :character      Mode  :character        
##                                                
##                                                
##                                                
## 
Visualitzacions:

Aquesta taula també té diverses variables numèriques i categòriques. Per això, farem com hem fet anteriorment i les agruparem per similitud.

  • BuildUpPlay Boxplot

Les primeres variables que analitzem són les que tenen a veure amb com un equip construeix el seu joc.

Aquí podem veure les gràfiques Box Plot de les variables buildUpPlaySpeed, buildUpPlayDribbling i buildUpPlayPassing:

box_plot_attr <- c("buildUpPlaySpeed",
                    "buildUpPlayDribbling",
                    "buildUpPlayPassing")
df_team_attributes_aux <- df_team_attributes[, which(names(df_team_attributes) %in% box_plot_attr)]
boxplot(df_team_attributes_aux)

Es pot observar com tenen una variabilitat molt gran i que tenen pocs o cap valor extrems.

  • ChanceCreation Boxplot

Els segons atributs que analitzem són els que tenen a veure amb la creació d’oportunitats. Aquests són chanceCreationPassing, chanceCreationCrossing i `chanceCreationShooting.

Seguidament, podem veure les seues gràfiques Box Plot:

box_plot_attr <- c("chanceCreationPassing",
                    "chanceCreationCrossing",
                    "chanceCreationShooting")
df_team_attributes_aux <- df_team_attributes[, which(names(df_team_attributes) %in% box_plot_attr)]
boxplot(df_team_attributes_aux)

Són uns resultats bastant similars als anteriors, una gran variabilitat i amb pocs valors extrems.

  • DefenceAggression Boxplot

Ara estudiem les variables que fan referència a l’agressivitat en defensa. Aquestes són chanceCreationPassing, chanceCreationCrossing i chanceCreationShooting.

A continuació, es mostren els seus gràfics:

box_plot_attr <- c("defencePressure",
                    "defenceAggression",
                    "defenceTeamWidth")
df_team_attributes_aux <- df_team_attributes[, which(names(df_team_attributes) %in% box_plot_attr)]
boxplot(df_team_attributes_aux)

Tornem a tenir gràfics similars als anteriors. En tots aquests casos costa molt extreure’n conclusions per què són variables abstractes i amb resultats molt similars. L’únic que podem deduir és que els equips juguen de formes molt diferents i per això obtenim aquesta variabilitat tan elevada.

  • BuildUpPlay Histograma

Mostrem l’histograma de les variables relacionades amb la forma de construir el joc:

hist_list <- list()

dist_attr <- c("buildUpPlaySpeedClass",
                "buildUpPlayDribblingClass",
                "buildUpPlayPassingClass",
                "buildUpPlayPositioningClass")
df_team_attributes_aux <- df_team_attributes[, which(names(df_team_attributes) %in% dist_attr)]

for(i in seq_len(ncol(df_team_attributes_aux))) {
  col <- names(df_team_attributes_aux)[i]
  ggp <- ggplot(df_team_attributes_aux, aes_string(x = col)) +
    geom_histogram(stat = "count") 
      hist_list[[i]] <- ggp  # afegim cada plot a la llista buida
}
multiplot(plotlist = hist_list, cols = 1)

Podem veure que les distribucions són molt desiguals en totes les variables, excepte en la buildUpPlayDribblingClass que hi ha dos categories amb una quantitat similar. Això implica que els equips construeixen el joc de forma semblant.

  • ChanceCreation Histograma

A continuació, es mostra l’histograma de les variables que tenen a veure amb la creació d’oportunitats:

hist_list <- list()

dist_attr <- c("chanceCreationPassingClass",
                "chanceCreationCrossingClass",
                "chanceCreationShootingClass",
                "chanceCreationPositioningClass")
df_team_attributes_aux <- df_team_attributes[, which(names(df_team_attributes) %in% dist_attr)]

for(i in seq_len(ncol(df_team_attributes_aux))) {
  col <- names(df_team_attributes_aux)[i]
  ggp <- ggplot(df_team_attributes_aux, aes_string(x = col)) +
    geom_histogram(stat = "count") 
      hist_list[[i]] <- ggp  # afegim cada plot a la llista buida
}
multiplot(plotlist = hist_list, cols = 1)

Un altre cop, veiem com en totes les variables hi ha una categoria que destaca clarament, que acostuma a ser la Normal.

  • Defence Histograma

Finalment, es genera l’histograma pels atributs defensius:

hist_list <- list()

dist_attr <- c("defencePressureClass",
                "defenceAggressionClass",
                "defenceTeamWidthClass",
                "defenceDefenderLineClass")
df_team_attributes_aux <- df_team_attributes[, which(names(df_team_attributes) %in% dist_attr)]

for(i in seq_len(ncol(df_team_attributes_aux))) {
  col <- names(df_team_attributes_aux)[i]
  ggp <- ggplot(df_team_attributes_aux, aes_string(x = col)) +
    geom_histogram(stat = "count") 
      hist_list[[i]] <- ggp  # afegim cada plot a la llista buida
}
multiplot(plotlist = hist_list, cols = 1)

Tampoc hi ha sorpreses, la majoria dels equips comparteixen les mateixes categories. Això sorprèn per què en l’anàlisi de les variables numèriques ens ha costat veure que tinguessin valors similars, però quan s’agrupen en ca veiem que són pocs els equips que es mouen de la normalitat.

1.3.3 Anàlisi multivariant

L’anàlisi multivariant és una branca de l’estadística que consisteix a examinar diverses variables alhora.

Com hem vist en l’anàlisi preliminar, no totes les taules tenen atributs que es puguin analitzar. Algunes només són taules auxiliars que ens serveixen per a entendre millor les dades. Per tant, només es faran servir aquelles taules que tinguin atributs rellevants. Aquestes són Match, Player, Player_Attributes i Team_Attributes.

En aquesta secció s’analitza la matriu de correlacions per a cada una de les taules. Aquesta matriu ens indica la correlació que hi ha entre les diverses variables.

Fem servir les següents funcions que mostren a la part baixa de la matriu l’scatter plot de dos variables, a la diagonal mostren la distribució de la variable i a la part alta de la matriu mostren la correlació entre dos variables:

# https://r-coder.com/correlation-plot-r/
panel_hist <- function(x, ...) {
    usr <- par("usr")
    on.exit(par(usr))
    par(usr = c(usr[1:2], 0, 1.5))
    his <- hist(x, plot = FALSE)
    breaks <- his$breaks
    nB <- length(breaks)
    y <- his$counts
    y <- y / max(y)
    rect(breaks[-nB], 0, breaks[-1], y, col = rgb(0.8, 0.5, 0.5, alpha = 1.0), ...)
}

panel_corr <- function(x, y, digits = 2, prefix = "", cex_cor, ...) {
    usr <- par("usr")
    on.exit(par(usr))
    par(usr = c(0, 1, 0, 1))
    corr <- abs(cor(x, y)) # Remove abs function if desired
    txt <- paste0(prefix, format(c(corr, 0.123456789), digits = digits)[1])
    if (missing(cex_cor)) {
      cex_cor <- 0.4 / strwidth(txt)
    }
    text(0.5, 0.5, txt,
         cex = 1 + cex_cor * corr) # Resize the text by level of correlation
}

Match:

  • Matriu de correlacions
corr_attr <- c("home_team_goal",
                "away_team_goal")
df_match_corr <- df_match[, which(names(df_match) %in% corr_attr)]

# Plot correlation matrix
pairs(df_match_corr,
      upper.panel = panel_corr,
      diag.panel = panel_hist)

En aquesta taula només tenim dos variables numèriques i veiem que aquestes no estan correlacionades.

Player:

  • Matriu de correlacions
corr_attr <- c("height", "weight")
df_player_corr <- df_player[, which(names(df_player) %in% corr_attr)]

# Plot correlation matrix
pairs(df_player_corr,
      upper.panel = panel_corr,
      diag.panel = panel_hist)

Aquesta taula també té només dos atributs numèrics. Veiem que estan força correlacionats. És lògic, ja que, com més alçada més pesa una persona.

Player Attributes:

  • Matriu de correlacions
corr_attr <- c("overall_rating", "potential")
df_player_attributes_corr <- df_player_attributes[, which(names(df_player_attributes) %in% corr_attr)]

df_player_attributes_corr <- na.omit(df_player_attributes_corr)

# Plot correlation matrix
pairs(df_player_attributes_corr,
      upper.panel = panel_corr,
      diag.panel = panel_hist)

En aquest cas, tenim només les variables overall_rating i potential. I veiem que també estan bastant correlacionades. És lògic perquè com més valoració global té un jugador, més pot arribar a millorar.

Team Attributes:

  • Matriu de correlacions
corr_attr <- c("buildUpPlaySpeed",
                "buildUpPlayDribbling",
                "buildUpPlayPassing",
                "chanceCreationPassing",
                "chanceCreationCrossing",
                "chanceCreationShooting",
                "defencePressure",
                "defenceAggression",
                "defenceTeamWidth")
df_team_attributes_corr <- df_team_attributes[, which(names(df_team_attributes) %in% corr_attr)]
df_team_attributes_corr <- na.omit(df_team_attributes_corr)

# Plot correlation matrix
pairs(df_team_attributes_corr,
      upper.panel = panel_corr,
      diag.panel = panel_hist)

En aquesta taula ja tenim moltes més variables. En general hi ha cap parell de variables amb una forta correlació. És estrany perquè hi ha variables amb una temàtica semblant. Els parells amb més correlació són defencePressure i defenceAggression, defencePressure i defenceTeamWidth, i buildUpPlaySpeed i buildUpPlayPassing.

1.4 Neteja i preparació

L’objectiu d’aquesta secció és deixar les dades polides i ben netes per poder aplicar els algorismes de mineria de dades desitjats.

1.4.1 Esborrar variables buides

El primer pas que duem a terme és el d’esborrar variables buides o innecessàries.

Gràcies a l’anterior anàlisi ja coneixem quines variables contenen massa registres buits per a fer-les servir. També hem observat que algunes estan mal formatades, o bé contenen paràmetres incorrectes o bé hi ha categories de més. S’aplicarà la millor estratègia en cada cas.

Match:

Com s’ha explicat anteriorment, les quotes d’apostes no ens interessen, per això les podem suprimir del nostre dataset.

Abans de res, ens guardem dataframe original per si el volem recuperar més endavant:

df_match_org <- df_match

Ara procedim a suprimir les variables d’apostes:

odds_attr <- c("B365H", "B365D", "B365A",
                "BWH", "BWD", "BWA",
                "IWH", "IWD", "IWA",
                "LBH", "LBD", "LBA",
                "PSH", "PSD", "PSA",
                "WHH", "WHD", "WHA",
                "SJH", "SJD", "SJA",
                "VCH", "VCD", "VCA",
                "GBH", "GBD", "GBA",
                "BSH", "BSD", "BSA")

df_match <- df_match[, -which(names(df_match) %in% odds_attr)]

I a continuació, suprimim les variables goal, shoton, shotoff, foulcommit, card, cross, corner i possession que tenen massa valors NA i impedeixen poder fer cap anàlisi:

empty_attr <- c("goal",
                "shoton",
                "shotoff",
                "foulcommit",
                "card",
                "cross",
                "corner",
                "possession")

df_match <- df_match[, -which(names(df_match) %in% empty_attr)]

Player Attributes:

Com hem vist en l’anàlisi univariant, aquesta taula té dos atributs amb categories equivocades. Aquests són defensive_work_rate i attacking_work_rate.

Ens guardem el dataframe original:

df_player_attributes_org <- df_player_attributes

Convertim en NA les categories que no són high, low i medium:

keep_values <- c("high", "medium", "low")

df_player_attributes$defensive_work_rate[!(df_player_attributes$defensive_work_rate %in% keep_values)] <- NA
df_player_attributes$attacking_work_rate[!(df_player_attributes$attacking_work_rate %in% keep_values)] <- NA

És possible que després de la conversió hi hagi massa valors NA en aquestes variables. Tornem a comprovar la quantitat de valors buits que hi ha en la taula:

sort(colMeans(is.na(df_player_attributes) | df_player_attributes == ""), decreasing = TRUE)
## defensive_work_rate attacking_work_rate             volleys               curve 
##         0.041629977         0.040852711         0.014746328         0.014746328 
##             agility             balance             jumping              vision 
##         0.014746328         0.014746328         0.014746328         0.014746328 
##      sliding_tackle      overall_rating           potential      preferred_foot 
##         0.014746328         0.004544022         0.004544022         0.004544022 
##            crossing           finishing    heading_accuracy       short_passing 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##           dribbling  free_kick_accuracy        long_passing        ball_control 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##        acceleration        sprint_speed           reactions          shot_power 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##             stamina            strength          long_shots          aggression 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##       interceptions         positioning           penalties             marking 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##     standing_tackle           gk_diving         gk_handling          gk_kicking 
##         0.004544022         0.004544022         0.004544022         0.004544022 
##      gk_positioning         gk_reflexes                  id  player_fifa_api_id 
##         0.004544022         0.004544022         0.000000000         0.000000000 
##       player_api_id                date 
##         0.000000000         0.000000000

Veiem que el percentatge de valors buits segueix sent molt baix, per tant, no ens preocupem.

1.4.2 Unir taules

En aquest pas la intenció és reduir el nombre total de taules. Hem pogut veure que hi ha moltes taules redundants. Per això, sempre que es pugui, unirem aquestes taules.

1.4.2.1 Country i League

Com que les taules Country i League són molt similars i fan referència als mateixos registres té molt sentit unir-les.

Ens guardem el dataframe original:

df_league_org <- df_league

Realitzem un outer-join amb totes dos taules i la guardem a la taula League:

df_league <- merge(x = df_league, y = df_country, by = "id", all = TRUE)

Esborrem la columna country_id:

df_league <- df_league[, -which(names(df_league) == "country_id")]

I finalment, canviem els noms a les columnes name.x i name.y:

colnames(df_league) <- c("id", "league_name", "country_name")
df_league
##       id              league_name country_name
## 1      1   Belgium Jupiler League      Belgium
## 2   1729   England Premier League      England
## 3   4769           France Ligue 1       France
## 4   7809    Germany 1. Bundesliga      Germany
## 5  10257            Italy Serie A        Italy
## 6  13274   Netherlands Eredivisie  Netherlands
## 7  15722       Poland Ekstraklasa       Poland
## 8  17642 Portugal Liga ZON Sagres     Portugal
## 9  19694  Scotland Premier League     Scotland
## 10 21518          Spain LIGA BBVA        Spain
## 11 24558 Switzerland Super League  Switzerland

1.4.2.2 Player i Player Attributes

Basant-nos en el contingut d’aquestes dos taules, semblaria lògic pensar que haurien d’estar unides en una de sola.

Però, una cosa que ens sorprèn és que la taula Player té 11.060 registres i la taula Player_Attributes en té 183.978. És una diferència molt gran.

Estudiem si la taula Player_Attributes conté registres duplicats. Ho fem amb la comanda d’R duplicated que indica si un registre està duplicat o no:

any(duplicated(df_player_attributes$id))
## [1] FALSE
any(duplicated(df_player_attributes$player_api_id))
## [1] TRUE
any(duplicated(df_player_attributes$player_fifa_api_id))
## [1] TRUE

Veiem que la variable id no conté cap valor repetit, com és lògic. Però, en canvi, la variable player_api_id sí que conté duplicats, però en tractar-se d’un identificador haurien de ser valors únics.

Hem de tenir en compte que hi ha una variable date, fins ara no sabíem ben bé què indicava, però ara podem deduir que ens mostra els atributs d’un jugador en una data determinada.

Per tant, si volguéssim podríem estudiar l’evolució dels jugadors en el temps, encara que les dates no són gaire consistents entre jugadors. També caldrà tenir-ho en compte a l’hora de calcular nous atributs.

Pel que fa a unir les dos taules no hi ha problema, és cert que no sabem en quina data es van mesurar el pes i l’alçada dels jugadors però no creiem que hi hagi gaire diferència.

Procedim a fer un outer join de les taules Player i Player_Attributes que emmagatzemem en la taula Player_Attributes:

df_player_attributes_org <- df_player_attributes
df_player_attributes <- merge(x = df_player_attributes, y = df_player, by = c("player_api_id", "player_fifa_api_id"), all = TRUE)

1.4.2.3 Team i Team Attributes

Aquestes dos taules són molt similars a les anteriors, ja que, la taula Team_Attributes conté moltes més observacions que la taula Team, però, és perquè cada equip té múltiples registres segons la data.

Unim totes dos taules fent servir el mètode anterior:

df_team_attributes_org <- df_team_attributes
df_team_attributes <- merge(x = df_team_attributes, y = df_team, by = c("team_api_id", "team_fifa_api_id"), all = TRUE)

1.4.3 Afegir noves variables

L’últim pas consisteix a crear nous atributs que serveixin per a facilitat l’anàlisi.

Match:

En aquesta taula ens interessa crear una variable que indiqui els gols totals que s’han marcat al partit. Es calcularà sumant els gols de l’equip local i els gols de l’equip visitant.

Creem la nova variable total_goals sumant home_team_goal i away_team_goal:

df_match$total_goals <- (df_match$home_team_goal + df_match$away_team_goal)
summary(df_match$total_goals)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   2.000   3.000   2.706   4.000  12.000

I comprovem que el càlcul és correcte:

head(df_match[c("home_team_goal", "away_team_goal", "total_goals")])
##   home_team_goal away_team_goal total_goals
## 1              1              1           2
## 2              0              0           0
## 3              0              3           3
## 4              5              0           5
## 5              1              3           4
## 6              1              1           2

Player Attributes:

En aquesta taula podem calcular múltiples atributs nous relacionats amb els jugadors.

El primer que podem fer és crear una variable que combini l’alçada i el pes. Podem fer servir el càlcul de l’índex de massa corporal (IMC) o body mass index (BMI), en anglès.

La fórmula és la següent: \[ {massa (lb)} \over {(alçada/100)^2 (cm) · 2.2046} \]

Afegim la variable bmi:

df_player_attributes$bmi <- ((df_player_attributes$weight) / (((df_player_attributes$height/100)**2)*2.2046))
summary(df_player_attributes$bmi)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   17.59   22.18   23.10   23.12   23.99   30.87     212

Convertim a int les variables birth_day, birth_month i birth_year:

df_player_attributes$birth_day <- as.integer(df_player_attributes$birth_day)
df_player_attributes$birth_month <- as.integer(df_player_attributes$birth_month)
df_player_attributes$birth_year <- as.integer(df_player_attributes$birth_year)
str(df_player_attributes)
## 'data.frame':    183978 obs. of  51 variables:
##  $ player_api_id      : int  2625 2625 2625 2625 2625 2625 2625 2625 2625 2625 ...
##  $ player_fifa_api_id : int  148544 148544 148544 148544 148544 148544 148544 148544 148544 148544 ...
##  $ id.x               : int  139845 139849 139846 139847 139844 139857 139856 139855 139850 139848 ...
##  $ date               : chr  "2014-11-07 00:00:00" "2013-02-15 00:00:00" "2014-09-18 00:00:00" "2013-06-07 00:00:00" ...
##  $ overall_rating     : int  61 58 61 61 61 63 63 60 58 61 ...
##  $ potential          : int  61 58 61 61 61 64 64 64 58 61 ...
##  $ preferred_foot     : chr  "right" "right" "right" "right" ...
##  $ attacking_work_rate: chr  "medium" "medium" "medium" "medium" ...
##  $ defensive_work_rate: chr  "medium" "medium" "medium" "medium" ...
##  $ crossing           : int  50 52 50 50 50 48 48 48 52 50 ...
##  $ finishing          : int  47 47 47 47 47 48 48 48 47 47 ...
##  $ heading_accuracy   : int  46 46 46 46 46 47 47 47 46 46 ...
##  $ short_passing      : int  52 53 52 52 52 64 64 64 53 52 ...
##  $ volleys            : int  39 37 39 39 39 38 38 38 37 39 ...
##  $ dribbling          : int  50 56 50 50 50 57 57 57 56 50 ...
##  $ curve              : int  51 49 51 51 51 50 50 50 49 51 ...
##  $ free_kick_accuracy : int  50 50 50 50 50 46 51 51 50 50 ...
##  $ long_passing       : int  64 66 64 64 64 67 67 67 66 64 ...
##  $ ball_control       : int  62 58 62 62 62 57 57 57 58 62 ...
##  $ acceleration       : int  67 67 67 67 67 67 67 67 67 67 ...
##  $ sprint_speed       : int  63 63 63 63 63 64 64 64 63 63 ...
##  $ agility            : int  74 74 74 74 74 59 59 59 74 74 ...
##  $ reactions          : int  49 49 49 49 49 52 52 52 49 49 ...
##  $ balance            : int  76 76 76 76 76 49 49 49 76 76 ...
##  $ shot_power         : int  68 68 68 68 68 61 61 61 68 68 ...
##  $ jumping            : int  63 63 63 63 63 56 56 56 55 63 ...
##  $ stamina            : int  77 77 77 77 77 78 78 78 77 77 ...
##  $ strength           : int  56 56 56 56 56 56 56 56 56 56 ...
##  $ long_shots         : int  54 58 54 54 54 59 59 59 58 54 ...
##  $ aggression         : int  71 71 71 71 71 72 72 72 71 71 ...
##  $ interceptions      : int  64 64 64 64 64 52 52 52 64 64 ...
##  $ positioning        : int  49 49 49 49 49 55 55 55 49 49 ...
##  $ vision             : int  55 55 55 55 55 56 56 56 55 55 ...
##  $ penalties          : int  66 66 66 66 66 46 46 46 66 66 ...
##  $ marking            : int  62 63 62 62 62 64 64 64 63 62 ...
##  $ standing_tackle    : int  63 63 63 63 63 66 66 66 63 63 ...
##  $ sliding_tackle     : int  54 52 54 54 54 63 63 63 52 54 ...
##  $ gk_diving          : int  12 12 12 12 12 14 14 14 12 12 ...
##  $ gk_handling        : int  11 11 11 11 11 11 24 24 11 11 ...
##  $ gk_kicking         : int  6 6 6 6 6 67 67 67 6 6 ...
##  $ gk_positioning     : int  8 8 8 8 8 9 24 24 8 8 ...
##  $ gk_reflexes        : int  8 8 8 8 8 10 24 24 8 8 ...
##  $ id.y               : int  8372 8372 8372 8372 8372 8372 8372 8372 8372 8372 ...
##  $ player_name        : chr  "Patryk Rachwal,18" "Patryk Rachwal,18" "Patryk Rachwal,18" "Patryk Rachwal,18" ...
##  $ birthday           : chr  "1981-01-27 00:00:00" "1981-01-27 00:00:00" "1981-01-27 00:00:00" "1981-01-27 00:00:00" ...
##  $ height             : num  175 175 175 175 175 ...
##  $ weight             : int  154 154 154 154 154 154 154 154 154 154 ...
##  $ birth_year         : int  1981 1981 1981 1981 1981 1981 1981 1981 1981 1981 ...
##  $ birth_month        : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ birth_day          : int  27 27 27 27 27 27 27 27 27 27 ...
##  $ bmi                : num  22.7 22.7 22.7 22.7 22.7 ...

També podem afegir el càlcul de l’edat dels jugadors l’any 2016:

df_player_attributes$age <- (2016 - df_player_attributes$birth_year)
summary(df_player_attributes$age)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   17.00   26.00   29.00   29.21   33.00   49.00     212

1.4.4 Normalització

La finalitat de normalitzar els valors és situar les dades sobre una escala de valors equivalents. D’aquesta forma es poden comparar tots els atributs, encara que originalment tinguessin rangs diferents.

En aquest cas, normalitzem els valors numèrics de les taules utilitzant la normalització basada en la desviació estandard. A més, també suprimim els registres que contenen NA.

Match:

num_attr <- c("home_team_goal",
              "away_team_goal",
              "total_goals")

df_match_aux <- df_match[, num_attr]
df_match_nor <- as.data.frame(scale(df_match_aux))
df_match_nor <- na.omit(df_match_nor)

summary(df_match_nor)
##  home_team_goal    away_team_goal     total_goals     
##  Min.   :-1.1908   Min.   :-1.0165   Min.   :-1.6177  
##  1st Qu.:-0.4198   1st Qu.:-1.0165   1st Qu.:-0.4219  
##  Median :-0.4198   Median :-0.1409   Median : 0.1761  
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
##  3rd Qu.: 0.3511   3rd Qu.: 0.7347   3rd Qu.: 0.7740  
##  Max.   : 6.5184   Max.   : 6.8637   Max.   : 5.5574

Player Attributes:

num_attr <- c("overall_rating", "potential",
              "crossing", "finishing",
              "heading_accuracy", "short_passing",
              "volleys", "dribbling",
              "curve", "free_kick_accuracy",
              "long_passing", "ball_control",
              "acceleration", "sprint_speed",
              "agility", "reactions",
              "balance", "shot_power",
              "jumping", "stamina",
              "strength", "long_shots",
              "aggression", "interceptions",
              "positioning", "vision",
              "penalties", "marking",
              "standing_tackle", "sliding_tackle",
              "gk_diving", "gk_handling",
              "gk_kicking", "gk_positioning",
              "gk_reflexes", "height",
              "weight", "birth_year",
              "birth_month", "birth_day",
              "bmi", "age")
df_player_attributes_aux <- df_player_attributes[, num_attr]
df_player_attributes_nor <- as.data.frame(scale(df_player_attributes_aux))
df_player_attributes_nor <- na.omit(df_player_attributes_nor)

summary(df_player_attributes_nor)
##  overall_rating        potential            crossing        
##  Min.   :-5.056002   Min.   :-5.227387   Min.   :-3.136902  
##  1st Qu.:-0.653306   1st Qu.:-0.676603   1st Qu.:-0.585014  
##  Median : 0.056807   Median : 0.081861   Median : 0.226951  
##  Mean   : 0.002695   Mean   : 0.001331   Mean   : 0.001561  
##  3rd Qu.: 0.624897   3rd Qu.: 0.688632   3rd Qu.: 0.748928  
##  Max.   : 3.607368   Max.   : 3.570795   Max.   : 2.314859  
##    finishing          heading_accuracy    short_passing      
##  Min.   :-2.5695591   Min.   :-3.412356   Min.   :-4.186937  
##  1st Qu.:-0.8362479   1st Qu.:-0.501308   1st Qu.:-0.382531  
##  Median : 0.1617191   Median : 0.165807   Median : 0.181085  
##  Mean   : 0.0005347   Mean   :-0.000257   Mean   : 0.001961  
##  3rd Qu.: 0.7920141   3rd Qu.: 0.650982   3rd Qu.: 0.674248  
##  Max.   : 2.4728007   Max.   : 2.470387   Max.   : 2.435548  
##     volleys            dribbling             curve          
##  Min.   :-2.654842   Min.   :-3.278455   Min.   :-2.791754  
##  1st Qu.:-0.792504   1st Qu.:-0.404355   1st Qu.:-0.655446  
##  Median : 0.138666   Median : 0.271904   Median : 0.166212  
##  Mean   :-0.000247   Mean   : 0.002797   Mean   :-0.000454  
##  3rd Qu.: 0.795961   3rd Qu.: 0.722743   3rd Qu.: 0.768760  
##  Max.   : 2.384427   Max.   : 2.131615   Max.   : 2.247743  
##  free_kick_accuracy    long_passing        ball_control      
##  Min.   :-2.7131919   Min.   :-3.756297   Min.   :-3.842215  
##  1st Qu.:-0.7504004   1st Qu.:-0.560624   1st Qu.:-0.288805  
##  Median : 0.0347162   Median : 0.134088   Median : 0.237626  
##  Mean   :-0.0006399   Mean   : 0.000393   Mean   : 0.001989  
##  3rd Qu.: 0.7637530   3rd Qu.: 0.689857   3rd Qu.: 0.632449  
##  Max.   : 2.6704647   Max.   : 2.773992   Max.   : 2.211742  
##   acceleration        sprint_speed          agility         
##  Min.   :-4.441031   Min.   :-4.459227   Min.   :-4.243356  
##  1st Qu.:-0.512916   1st Qu.:-0.481414   1st Qu.:-0.615296  
##  Median : 0.103259   Median : 0.075479   Median : 0.156631  
##  Mean   : 0.002208   Mean   : 0.002237   Mean   :-0.000605  
##  3rd Qu.: 0.719434   3rd Qu.: 0.711930   3rd Qu.: 0.696980  
##  Max.   : 2.259871   Max.   : 2.303055   Max.   : 2.318028  
##    reactions            balance            shot_power       
##  Min.   :-5.363355   Min.   :-4.071709   Min.   :-3.706718  
##  1st Qu.:-0.557453   1st Qu.:-0.550363   1st Qu.:-0.483939  
##  Median : 0.097898   Median : 0.138596   Median : 0.197803  
##  Mean   : 0.002677   Mean   :-0.000522   Mean   : 0.002184  
##  3rd Qu.: 0.644023   3rd Qu.: 0.674453   3rd Qu.: 0.693615  
##  Max.   : 3.265424   Max.   : 2.358575   Max.   : 2.181051  
##     jumping             stamina             strength          long_shots       
##  Min.   :-4.812422   Min.   :-4.332504   Min.   :-4.75673   Min.   :-2.849641  
##  1st Qu.:-0.633162   1st Qu.:-0.458673   1st Qu.:-0.61501   1st Qu.:-0.671825  
##  Median : 0.093666   Median : 0.148987   Median : 0.13050   Median : 0.253747  
##  Mean   : 0.000782   Mean   : 0.000357   Mean   : 0.00065   Mean   : 0.001224  
##  3rd Qu.: 0.638787   3rd Qu.: 0.680690   3rd Qu.: 0.71034   3rd Qu.: 0.743755  
##  Max.   : 2.637563   Max.   : 2.199839   Max.   : 2.36703   Max.   : 2.322672  
##    aggression        interceptions        positioning       
##  Min.   :-3.415145   Min.   :-2.622567   Min.   :-2.915528  
##  1st Qu.:-0.618293   1st Qu.:-0.925920   1st Qu.:-0.584689  
##  Median : 0.189686   Median : 0.205177   Median : 0.228395  
##  Mean   :-0.000568   Mean   :-0.004663   Mean   :-0.003868  
##  3rd Qu.: 0.749056   3rd Qu.: 0.822140   3rd Qu.: 0.716245  
##  Max.   : 2.240710   Max.   : 2.261719   Max.   : 2.125589  
##      vision            penalties            marking          
##  Min.   :-3.755496   Min.   :-3.409380   Min.   :-2.1562540  
##  1st Qu.:-0.585942   1st Qu.:-0.643487   1st Qu.:-1.0256540  
##  Median : 0.140415   Median : 0.128390   Median : 0.1520543  
##  Mean   :-0.000352   Mean   :-0.004523   Mean   :-0.0006505  
##  3rd Qu.: 0.734706   3rd Qu.: 0.707298   3rd Qu.: 0.9057876  
##  Max.   : 2.583613   Max.   : 2.636990   Max.   : 2.2248209  
##  standing_tackle      sliding_tackle        gk_diving        
##  Min.   :-2.2971483   Min.   :-2.129818   Min.   :-0.812571  
##  1st Qu.:-0.9938349   1st Qu.:-1.064943   1st Qu.:-0.456815  
##  Median : 0.2629315   Median : 0.231427   Median :-0.278936  
##  Mean   :-0.0000281   Mean   : 0.000648   Mean   : 0.000394  
##  3rd Qu.: 0.8680412   3rd Qu.: 0.879612   3rd Qu.:-0.101058  
##  Max.   : 2.0782608   Max.   : 2.175981   Max.   : 4.701655  
##   gk_handling          gk_kicking       gk_positioning      gk_reflexes       
##  Min.   :-0.949345   Min.   :-0.93219   Min.   :-0.93993   Min.   :-0.897854  
##  1st Qu.:-0.508188   1st Qu.:-0.60590   1st Qu.:-0.50513   1st Qu.:-0.490834  
##  Median :-0.319121   Median :-0.41945   Median :-0.31878   Median :-0.316397  
##  Mean   :-0.005326   Mean   :-0.01712   Mean   :-0.00525   Mean   :-0.004893  
##  3rd Qu.:-0.067031   3rd Qu.:-0.27960   3rd Qu.:-0.07032   3rd Qu.:-0.083814  
##  Max.   : 4.848713   Max.   : 3.54271   Max.   : 4.96099   Max.   : 4.625994  
##      height              weight            birth_year      
##  Min.   :-3.814343   Min.   :-3.430241   Min.   :-3.65544  
##  1st Qu.:-0.637160   1st Qu.:-0.647351   1st Qu.:-0.57270  
##  Median : 0.157136   Median :-0.051018   Median : 0.04384  
##  Mean   : 0.000692   Mean   : 0.000209   Mean   : 0.02026  
##  3rd Qu.: 0.554284   3rd Qu.: 0.677834   3rd Qu.: 0.86591  
##  Max.   : 4.128615   Max.   : 4.918428   Max.   : 2.51003  
##   birth_month           birth_day               bmi           
##  Min.   :-1.4446531   Min.   :-1.6368437   Min.   :-3.928781  
##  1st Qu.:-0.8621775   1st Qu.:-0.8457496   1st Qu.:-0.704882  
##  Median : 0.0115360   Median :-0.0546556   Median :-0.012492  
##  Mean   :-0.0005017   Mean   :-0.0002661   Mean   :-0.000524  
##  3rd Qu.: 0.8852494   3rd Qu.: 0.8494518   3rd Qu.: 0.652160  
##  Max.   : 1.7589629   Max.   : 1.7535593   Max.   : 5.799199  
##       age          
##  Min.   :-2.51003  
##  1st Qu.:-0.86591  
##  Median :-0.04384  
##  Mean   :-0.02026  
##  3rd Qu.: 0.57270  
##  Max.   : 3.65544

Team Attributes:

num_attr <- c("buildUpPlaySpeed",
              "buildUpPlayDribbling",
              "buildUpPlayPassing",
              "chanceCreationPassing",
              "chanceCreationCrossing",
              "chanceCreationShooting",
              "defencePressure",
              "defenceAggression",
              "defenceTeamWidth")

df_team_attributes_aux <- df_team_attributes[, num_attr]
df_team_attributes_nor <- as.data.frame(scale(df_team_attributes_aux))
df_team_attributes_nor <- na.omit(df_team_attributes_nor)

summary(df_team_attributes_nor)
##  buildUpPlaySpeed  buildUpPlayDribbling buildUpPlayPassing
##  Min.   :-2.2919   Min.   :-2.54253     Min.   :-2.61473  
##  1st Qu.:-0.3865   1st Qu.:-0.68270     1st Qu.:-0.68744  
##  Median : 0.1332   Median : 0.04057     Median : 0.13855  
##  Mean   : 0.1419   Mean   : 0.00000     Mean   : 0.08093  
##  3rd Qu.: 0.8261   3rd Qu.: 0.66051     3rd Qu.: 0.59742  
##  Max.   : 2.3851   Max.   : 2.93364     Max.   : 2.80005  
##  chanceCreationPassing chanceCreationCrossing chanceCreationShooting
##  Min.   :-3.00800      Min.   :-2.77193       Min.   :-3.0955       
##  1st Qu.:-0.49854      1st Qu.:-0.51700       1st Qu.:-0.7716       
##  Median : 0.08056      Median :-0.06601       Median :-0.1907       
##  Mean   : 0.04050      Mean   :-0.05752       Mean   :-0.2554       
##  3rd Qu.: 0.65967      3rd Qu.: 0.56537       3rd Qu.: 0.2935       
##  Max.   : 2.39699      Max.   : 2.36932       Max.   : 2.5205       
##  defencePressure    defenceAggression  defenceTeamWidth  
##  Min.   :-2.25058   Min.   :-2.59303   Min.   :-2.42157  
##  1st Qu.:-0.68612   1st Qu.:-0.53923   1st Qu.:-0.43718  
##  Median :-0.09945   Median :-0.12847   Median :-0.01941  
##  Mean   :-0.07666   Mean   :-0.07807   Mean   :-0.05999  
##  3rd Qu.: 0.48721   3rd Qu.: 0.48767   3rd Qu.: 0.39835  
##  Max.   : 2.54056   Max.   : 2.33610   Max.   : 2.17386

1.5 Reducció de la dimensionalitat

Un cop hem dut a terme les anteriors anàlisis i transformacions del nostre joc de dades, encara podem aplicar-li mètodes de reducció de la dimensionalitat. La finalitat és reduir el nombre d’atributs que tenim actualment, sense que el model perdi qualitat.

1.5.1 PCA (Principal component analysis)

La tècnica que fem servir per a reduir el nombre de variables és el Principal Component Analysis (PCA). Fent servir aquesta tècnica es pretén crear combinacions lineals de les característiques del dataset original. Aquestes noves combinacions són anomenades components. El que es fa és incorporar els components principals, que són aquells que acumulen una variabilitat superior al 90%.

En aquesta secció calculem els components principals per a cadascuna de les taules que han sigut normalitzades anteriorment, fem una visualització dels resultats i els interpretem.

En aquest exercici fem servir el mètode de Kàiser per decidir quines variables hem d’escollir. Ens quedem amb totes les variables que tinguin una variància superior a 1. Fent ús d’aquest mètode és fàcil que sobreestimem el nombre de components, per tant, caldrà tenir-ho en compte quan analitzem els resultats.

Match:

Calcular els components principals

Utilitzem la funció prcomp per fer una anàlisi de les components principals de la taula:

match_pca <- prcomp(df_match_nor, center = TRUE, scale. = TRUE)

summary(match_pca)
## Importance of components:
##                           PC1    PC2       PC3
## Standard deviation     1.3916 1.0312 1.142e-13
## Proportion of Variance 0.6455 0.3545 0.000e+00
## Cumulative Proportion  0.6455 1.0000 1.000e+00

Podem veure que tenim 4 components principals, i el primer de tots explica el 0,4843 de variabilitat del total de dades, en canvi, el quart no n’explica res.

Per poder fer servir el mètode de Kàiser hem de calcular la variància dels components principals a partir de la desviació estàndard:

var_acc <- match_pca$sdev^2

var_acc
## [1] 1.936573e+00 1.063427e+00 1.304826e-26
var_acc > 1
## [1]  TRUE  TRUE FALSE

Seguint aquest mètode, escollim els components PCA1 i PCA2.

La funció get_pca_var(...) ens retorna un objecte que es pot fer servir per seleccionar els elements principals que hem triat. En aquest cas mostrem les coordenades que ens serveixen per Generació del un diagrama de dispersió:

if (!require("factoextra")) install.packages("factoextra"); library("factoextra")
match_var <- get_pca_var(match_pca)
head(match_var$coord[, 1:2])
##                    Dim.1        Dim.2
## home_team_goal 0.7385428  0.674206562
## away_team_goal 0.6254800 -0.780240174
## total_goals    0.9999509 -0.009906259
Mostrar el PCA

A continuació, mostrem un histograma per veure el pes total dels components sobre el conjunt de dades:

if (!require("corrplot")) install.packages("corrplot"); library("corrplot")
ev <- get_eig(match_pca)
ev
##         eigenvalue variance.percent cumulative.variance.percent
## Dim.1 1.936573e+00     6.455242e+01                    64.55242
## Dim.2 1.063427e+00     3.544758e+01                   100.00000
## Dim.3 1.304826e-26     4.349421e-25                   100.00000
fviz_eig(match_pca)

També podem visualitzar la qualitat de representació de les variables en el mapa de factors. Ho fem accedint a la variable cos2 de l’objecte que hem creat prèviament.

corrplot(match_var$cos2[, 1:2], is.corr = FALSE)

fviz_cos2(match_pca, choice = "var", axes = 1:2)

Veiem que totes 3 variables tenen un cos2 igual a 1. Per tant, sabem que amb 2 components es poden representar perfectament. Això té molt sentit perquè les 3 estan molt relacionades entre elles.

Finalment, també visualitzem la contribució de les variables PCA fent servir la funció fviz_pca_var:

fviz_pca_var(match_pca,
             col.var = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE
             )

En aquest cas és fàcil de veure que totes tres variables aporten per igual. També es veu com els gols marcats per l’equip local i els marcats per l’equip visitant no apunten cap a la mateixa direcció, però tampoc en direccions oposades. Això vol que estan una mica correlacionades. També veiem que totes dos estan bastant correlacionades amb els gols totals en un partit.

Player Attributes:

Calcular els components principals

Utilitzem la funció prcomp per fer una anàlisi de les components principals de la taula:

player_attributes_pca <- prcomp(df_player_attributes_nor, center = TRUE, scale. = TRUE)

summary(player_attributes_pca)
## Importance of components:
##                           PC1    PC2     PC3     PC4     PC5     PC6     PC7
## Standard deviation     4.0191 2.4067 1.98281 1.57964 1.41944 1.19891 1.05730
## Proportion of Variance 0.3846 0.1379 0.09361 0.05941 0.04797 0.03422 0.02662
## Cumulative Proportion  0.3846 0.5225 0.61611 0.67552 0.72349 0.75772 0.78433
##                            PC8     PC9    PC10    PC11    PC12    PC13    PC14
## Standard deviation     0.99847 0.98639 0.86410 0.81385 0.74289 0.67930 0.64533
## Proportion of Variance 0.02374 0.02317 0.01778 0.01577 0.01314 0.01099 0.00992
## Cumulative Proportion  0.80807 0.83124 0.84901 0.86478 0.87792 0.88891 0.89883
##                          PC15   PC16    PC17    PC18    PC19    PC20    PC21
## Standard deviation     0.5903 0.5798 0.55980 0.53885 0.53160 0.52293 0.49469
## Proportion of Variance 0.0083 0.0080 0.00746 0.00691 0.00673 0.00651 0.00583
## Cumulative Proportion  0.9071 0.9151 0.92259 0.92950 0.93623 0.94274 0.94857
##                           PC22    PC23    PC24    PC25    PC26    PC27    PC28
## Standard deviation     0.47293 0.45757 0.42917 0.41658 0.41298 0.39733 0.38596
## Proportion of Variance 0.00533 0.00498 0.00439 0.00413 0.00406 0.00376 0.00355
## Cumulative Proportion  0.95389 0.95888 0.96326 0.96740 0.97146 0.97521 0.97876
##                           PC29    PC30    PC31    PC32    PC33    PC34    PC35
## Standard deviation     0.36523 0.35904 0.32290 0.30496 0.28930 0.28678 0.24001
## Proportion of Variance 0.00318 0.00307 0.00248 0.00221 0.00199 0.00196 0.00137
## Cumulative Proportion  0.98194 0.98501 0.98749 0.98970 0.99170 0.99365 0.99503
##                           PC36    PC37    PC38    PC39    PC40    PC41
## Standard deviation     0.23441 0.21836 0.19792 0.18400 0.17922 0.03355
## Proportion of Variance 0.00131 0.00114 0.00093 0.00081 0.00076 0.00003
## Cumulative Proportion  0.99633 0.99747 0.99840 0.99921 0.99997 1.00000
##                             PC42
## Standard deviation     1.174e-14
## Proportion of Variance 0.000e+00
## Cumulative Proportion  1.000e+00

En aquest cas obtenim moltes components principals més, en concret n’hi ha 42. Però veiem que la primera ja explica un 0,3846 de la variabilitat del total de dades, i que a partir del tretzè component s’explica menys de 0,1099.

Per poder fer servir el mètode de Kàiser hem de calcular la variància dels components principals a partir de la desviació estàndard:

var_acc <- player_attributes_pca$sdev^2

var_acc
##  [1] 1.615303e+01 5.792055e+00 3.931530e+00 2.495269e+00 2.014816e+00
##  [6] 1.437382e+00 1.117893e+00 9.969422e-01 9.729734e-01 7.466722e-01
## [11] 6.623531e-01 5.518809e-01 4.614503e-01 4.164451e-01 3.485069e-01
## [16] 3.361424e-01 3.133754e-01 2.903616e-01 2.825983e-01 2.734522e-01
## [21] 2.447165e-01 2.236618e-01 2.093691e-01 1.841890e-01 1.735410e-01
## [26] 1.705494e-01 1.578678e-01 1.489674e-01 1.333914e-01 1.289109e-01
## [31] 1.042646e-01 9.300136e-02 8.369512e-02 8.224358e-02 5.760332e-02
## [36] 5.494848e-02 4.768119e-02 3.917153e-02 3.385595e-02 3.212029e-02
## [41] 1.125377e-03 1.377781e-28
var_acc > 1
##  [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [37] FALSE FALSE FALSE FALSE FALSE FALSE

Seguint aquest mètode, escollim els components PCA1, PCA2, PCA3, PCA4, PCA5 i PCA6.

La funció get_pca_var(...) ens retorna un objecte que es pot fer servir per seleccionar els elements principals que hem triat. En aquest cas mostrem les coordenades que ens serveixen per Generació del un diagrama de dispersió:

player_attributes_var <- get_pca_var(player_attributes_pca)
head(player_attributes_var$coord[, 1:6])
##                      Dim.1       Dim.2      Dim.3       Dim.4       Dim.5
## overall_rating   0.4229777  0.12231348 0.70154747 -0.20918070  0.31384678
## potential        0.3691020 -0.02888391 0.46076640 -0.28350469  0.55860812
## crossing         0.8559132 -0.03440580 0.01983916 -0.13515477 -0.12933918
## finishing        0.7364945 -0.42436345 0.20199393  0.31906648  0.01653091
## heading_accuracy 0.5825595  0.49970965 0.04051559  0.37161005  0.14425648
## short_passing    0.9002185  0.14413285 0.05158948 -0.04138471 -0.03765355
##                        Dim.6
## overall_rating   -0.12057789
## potential        -0.25137418
## crossing         -0.07907206
## finishing         0.01246492
## heading_accuracy  0.11890413
## short_passing    -0.18543781
Mostrar el PCA

A continuació, mostrem un histograma per veure el pes total dels components sobre el conjunt de dades:

ev <- get_eig(player_attributes_pca)
ev
##          eigenvalue variance.percent cumulative.variance.percent
## Dim.1  1.615303e+01     3.845958e+01                    38.45958
## Dim.2  5.792055e+00     1.379061e+01                    52.25019
## Dim.3  3.931530e+00     9.360786e+00                    61.61098
## Dim.4  2.495269e+00     5.941117e+00                    67.55209
## Dim.5  2.014816e+00     4.797182e+00                    72.34928
## Dim.6  1.437382e+00     3.422339e+00                    75.77162
## Dim.7  1.117893e+00     2.661649e+00                    78.43326
## Dim.8  9.969422e-01     2.373672e+00                    80.80694
## Dim.9  9.729734e-01     2.316603e+00                    83.12354
## Dim.10 7.466722e-01     1.777791e+00                    84.90133
## Dim.11 6.623531e-01     1.577031e+00                    86.47836
## Dim.12 5.518809e-01     1.314002e+00                    87.79236
## Dim.13 4.614503e-01     1.098691e+00                    88.89105
## Dim.14 4.164451e-01     9.915359e-01                    89.88259
## Dim.15 3.485069e-01     8.297782e-01                    90.71237
## Dim.16 3.361424e-01     8.003389e-01                    91.51271
## Dim.17 3.133754e-01     7.461319e-01                    92.25884
## Dim.18 2.903616e-01     6.913372e-01                    92.95018
## Dim.19 2.825983e-01     6.728531e-01                    93.62303
## Dim.20 2.734522e-01     6.510767e-01                    94.27411
## Dim.21 2.447165e-01     5.826583e-01                    94.85677
## Dim.22 2.236618e-01     5.325282e-01                    95.38929
## Dim.23 2.093691e-01     4.984978e-01                    95.88779
## Dim.24 1.841890e-01     4.385451e-01                    96.32634
## Dim.25 1.735410e-01     4.131928e-01                    96.73953
## Dim.26 1.705494e-01     4.060699e-01                    97.14560
## Dim.27 1.578678e-01     3.758758e-01                    97.52147
## Dim.28 1.489674e-01     3.546843e-01                    97.87616
## Dim.29 1.333914e-01     3.175986e-01                    98.19376
## Dim.30 1.289109e-01     3.069306e-01                    98.50069
## Dim.31 1.042646e-01     2.482492e-01                    98.74894
## Dim.32 9.300136e-02     2.214318e-01                    98.97037
## Dim.33 8.369512e-02     1.992741e-01                    99.16964
## Dim.34 8.224358e-02     1.958180e-01                    99.36546
## Dim.35 5.760332e-02     1.371508e-01                    99.50261
## Dim.36 5.494848e-02     1.308297e-01                    99.63344
## Dim.37 4.768119e-02     1.135266e-01                    99.74697
## Dim.38 3.917153e-02     9.326554e-02                    99.84023
## Dim.39 3.385595e-02     8.060940e-02                    99.92084
## Dim.40 3.212029e-02     7.647687e-02                    99.99732
## Dim.41 1.125377e-03     2.679469e-03                   100.00000
## Dim.42 1.377781e-28     3.280430e-28                   100.00000
fviz_eig(player_attributes_pca)

També podem visualitzar la qualitat de representació de les variables en el mapa de factors. Ho fem accedint a la variable cos2 de l’objecte que hem creat prèviament.

corrplot(player_attributes_var$cos2[, 1:6], is.corr = FALSE)

fviz_cos2(player_attributes_pca, choice = "var", axes = 1:2)

En aquesta taula amb tantes variables és més complex que es puguin veure representades amb només 2 dimensions. Veiem que la que té un valor cos2 més alt és ball_control.

Finalment, també visualitzem la contribució de les variables PCA fent servir la funció fviz_pca_var:

fviz_pca_var(player_attributes_pca,
             col.var = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE
             )

Aquí podem veure que les variables que més contribueixen són ball_control, standing_tackle i dribbling entre algunes altres que també contribueixen molt. Les que menys són les variables que tenen a veure amb la data de naixement. També veiem que més o menys totes van en la mateixa direcció, excepte els atributs de porter. També veiem com el pes i l’alçada no estan gens relacionades amb l’agilitat.

Team Attributes:

Calcular els components principals

Utilitzem la funció prcomp per fer una anàlisi de les components principals de la taula:

team_attributes_pca <- prcomp(df_team_attributes_nor, center = TRUE, scale. = TRUE)

summary(team_attributes_pca)
## Importance of components:
##                           PC1    PC2    PC3    PC4     PC5     PC6     PC7
## Standard deviation     1.3390 1.2839 1.0826 1.0468 0.92843 0.85986 0.80526
## Proportion of Variance 0.1992 0.1832 0.1302 0.1218 0.09578 0.08215 0.07205
## Cumulative Proportion  0.1992 0.3824 0.5126 0.6344 0.73014 0.81229 0.88434
##                            PC8     PC9
## Standard deviation     0.76611 0.67382
## Proportion of Variance 0.06521 0.05045
## Cumulative Proportion  0.94955 1.00000

En aquesta taula hem aconseguit 9 components principals. Veiem com la proporció de la variabilitat total està molt més repartida. El primer component només n’explica 0,1992, i a partir d’aquí, la resta en van tenint menys.

Per poder fer servir el mètode de Kàiser hem de calcular la variància dels components principals a partir de la desviació estàndard:

var_acc <- team_attributes_pca$sdev^2

var_acc
## [1] 1.7929480 1.6483964 1.1720999 1.0958055 0.8619778 0.7393665 0.6484499
## [8] 0.5869280 0.4540279
var_acc > 1
## [1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE

Seguint aquest mètode, escollim els components PCA1, PCA2, PCA3 i PCA4.

La funció get_pca_var(...) ens retorna un objecte que es pot fer servir per seleccionar els elements principals que hem triat. En aquest cas mostrem les coordenades que ens serveixen per Generació del un diagrama de dispersió:

team_attributes_var <- get_pca_var(player_attributes_pca)
head(team_attributes_var$coord[, 1:4])
##                      Dim.1       Dim.2      Dim.3       Dim.4
## overall_rating   0.4229777  0.12231348 0.70154747 -0.20918070
## potential        0.3691020 -0.02888391 0.46076640 -0.28350469
## crossing         0.8559132 -0.03440580 0.01983916 -0.13515477
## finishing        0.7364945 -0.42436345 0.20199393  0.31906648
## heading_accuracy 0.5825595  0.49970965 0.04051559  0.37161005
## short_passing    0.9002185  0.14413285 0.05158948 -0.04138471
Mostrar el PCA

A continuació, mostrem un histograma per veure el pes total dels components sobre el conjunt de dades:

ev <- get_eig(team_attributes_pca)
ev
##       eigenvalue variance.percent cumulative.variance.percent
## Dim.1  1.7929480        19.921644                    19.92164
## Dim.2  1.6483964        18.315515                    38.23716
## Dim.3  1.1720999        13.023333                    51.26049
## Dim.4  1.0958055        12.175617                    63.43611
## Dim.5  0.8619778         9.577531                    73.01364
## Dim.6  0.7393665         8.215183                    81.22882
## Dim.7  0.6484499         7.204999                    88.43382
## Dim.8  0.5869280         6.521423                    94.95525
## Dim.9  0.4540279         5.044755                   100.00000
fviz_eig(team_attributes_pca)

També podem visualitzar la qualitat de representació de les variables en el mapa de factors. Ho fem accedint a la variable cos2 de l’objecte que hem creat prèviament:

corrplot(team_attributes_var$cos2[, 1:4], is.corr = FALSE)

fviz_cos2(team_attributes_pca, choice = "var", axes = 1:2)

En aquesta taula ja no hi ha tantes variables, però també hem obtingut valors cos2 relativament baixos. L’única variable que destaca amb un valor alt és defencePressure.

Finalment, també visualitzem la contribució de les variables PCA fent servir la funció fviz_pca_var:

fviz_pca_var(team_attributes_pca,
             col.var = "contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE
             )

En aquest gràfic es pot observar que la variable que més contribueix amb diferència és defencePressure. I les que menys chanceCreationShooting i buildUpPlayDribbling. En general, com és lògic, les variables del mateix grup tenen estan correlacionades.


2 Resolució de la pràctica 2


Un cop hem fet l’anàlisi i tractament previ de les dades, ja podem aplicar models de Machine Learning per a obtenir més coneixement sobre les dades.

Les taules que ens poden ser útils en aquest treball són les que contenen la informació bàsica modificada i les taules que han estat normalitzades:

2.1 Model no supervisat K-means

El primer que farem és aplicar un model no supervisat basat en el concepte de distància. En aquest cas fem servir K-means.

L’objectiu que tenim amb l’aplicació d’aquest algorisme és la d’aconseguir agrupar els jugadors segons les seues característiques.

En el futbol, s’acostuma a classificar els jugadors en quatre grans grups segons la posició del camp on juguen. Hi ha porters, defenses, migcampistes i davanters. Dins d’aquestes categories encara es poden fer més divisions. A més, hi ha molts matisos, ja que, depèn de l’estil de joc d’un equip les característiques dels jugadors hauran de ser diferents, encara que juguin en la mateixa posició. També s’ha de tenir en compte que un jugador pot ser versàtil i, per tant, pertànyer a diferents categories segons el partit.

El que esperem d’aquesta anàlisi és veure com hi ha un grup clarament diferenciat de la resta, els porters. I volem veure com s’agrupen la resta de jugadors i si s’observen les categories esmentades anteriorment.

2.1.1 Generació del model

Primer de tot, carreguem els paquets necessaris:

packages <- c("cluster", "fpc")

not_installed <- packages[!(packages %in% installed.packages())]
if (length(not_installed) > 0) {
  install.packages(not_installed, repos = "http:/cran.us.r-project.org")
}
lapply(packages, library, character.only = TRUE)
## [[1]]
##  [1] "cluster"    "corrplot"   "factoextra" "xfun"       "dplyr"     
##  [6] "Rmisc"      "plyr"       "lattice"    "ggplot2"    "RSQLite"   
## [11] "stats"      "graphics"   "grDevices"  "utils"      "datasets"  
## [16] "methods"    "base"      
## 
## [[2]]
##  [1] "fpc"        "cluster"    "corrplot"   "factoextra" "xfun"      
##  [6] "dplyr"      "Rmisc"      "plyr"       "lattice"    "ggplot2"   
## [11] "RSQLite"    "stats"      "graphics"   "grDevices"  "utils"     
## [16] "datasets"   "methods"    "base"

Ara ja podem implementar el model. Cal escollir el nombre de grups k en els que volem dividir les dades. Depenent de la k aconseguirem una qualitat del model més o menys bona. Com que no coneixem quina és la millor k el que farem serà provar diversos valors.

En aquesta taula hi ha múltiples valors per a cada jugador, això es deu al fet que es tenen registres dels futbolistes en anys diferents. No volem tenir diversos registres per a cada un, així que ens quedem amb el seus atributs més recents:

df_player_attributes$date <- as.Date(df_player_attributes$date, "%Y-%m-%d")

# https://stackoverflow.com/questions/30058708/select-row-with-most-recent-date-by-group
df_player_clustering <- df_player_attributes[
  tapply(1:nrow(df_player_attributes),
  df_player_attributes$player_api_id,
  function(ii) ii[which.max(df_player_attributes$date[ii])]), ]

No tots els atributs són útils a l’hora de realitzar l’agrupament. Així que ens quedem amb els que volem fer servir:

remove_attr <- c(
  "player_api_id",
  "player_fifa_api_id",
  "id.x",
  "date",
  "id.y",
  "player_name",
  "birthday"
)
df_player_clustering <- select(df_player_clustering, !all_of(remove_attr))

Convertim les variables categòriques a factor:

df_player_clustering[sapply(df_player_clustering, is.character)] <- lapply(
  df_player_clustering[sapply(df_player_clustering, is.character)],
  as.factor
)

df_player_clustering[sapply(df_player_clustering, is.factor)] <-
  data.matrix(df_player_clustering[sapply(df_player_clustering, is.factor)])

Aquest model no és compatible amb valors NA, NaN o Inf, així que els traiem:

df_player_clustering <-
  df_player_clustering[is.finite(rowSums(df_player_clustering)), ]

Quan es treballa amb aquest algorisme, és millor estandaritzar les dades:

df_player_clustering <- as.data.frame(scale(df_player_clustering))

Ara ja podem aplicar l’algorisme K-means amb. Inicialment fem servir 4 agrupacions:

player_kmeans4 <- kmeans(df_player_clustering, 4)
y_cluster4 <- player_kmeans4$cluster

I finalment visualitzem els clústers fent servir la funció clusplot:

clusplot(df_player_clustering, y_cluster4,
         color = TRUE, shade = TRUE, labels = 4, lines = 0)

És difícil distingir subgrups dins del grup més gran. La tria inicial del valor de grups a dividir no sembla gens bona, ja que, molts registres sembla que poden pertànyer a qualsevol altre clúster. Malgrat tot, la funció cusplot(...) mostra les dades amb els dos primers components principals del PCA. En aquest cas, veiem que només s’explica el 49,32 % de la variabilitat, així que podria ser que estiguessin ben classificats.

2.1.2 Anàlisi de la qualitat

Comprovarem diverses mesures de qualitat. A continuació mostrem quines són:

  • Silhouette: Ens permet interpretar i validar la consistència entre clústers de dades. Mostra la similitud d’un objecte respecte a l’agrupació a la qual pertany. Els valors van des de -1 fins a 1, i com més proper a 1 més similitud hi ha.
  • Withinss: La millor agrupació és la que té la menor suma dels quadrats de les distàncies dels punts de cada grup respecte al seu centre (withinss), amb la separació més gran entre centres de grups (betweenss).

Provem quin és el millor nombre d’agrupacions per a aquestes dades:

d <- daisy(df_player_clustering)
k_values <- 2:30
results_1 <- data.frame(k = k_values,
                      silhouette = double(length(k_values)),
                      withinss = double(length(k_values)))

for (i in k_values) {
  fit        <- kmeans(df_player_clustering, i)
  y_cluster  <- fit$cluster
  sk         <- silhouette(y_cluster, dist(df_player_clustering))
  results_1[i - 1, 2] <- mean(sk[, 3])
  results_1[i - 1, 3] <- fit$tot.withinss
}

results_1
##     k silhouette withinss
## 1   2 0.47565816 309818.4
## 2   3 0.19650567 257719.3
## 3   4 0.16717281 234636.6
## 4   5 0.15728781 222174.9
## 5   6 0.15161556 210538.6
## 6   7 0.13814698 203097.0
## 7   8 0.13735839 196381.7
## 8   9 0.13219652 192021.6
## 9  10 0.12844524 188040.8
## 10 11 0.12262052 185877.2
## 11 12 0.09066737 182692.3
## 12 13 0.08856499 179652.6
## 13 14 0.08505862 177424.1
## 14 15 0.08402146 175698.0
## 15 16 0.08202682 172970.0
## 16 17 0.11106797 171736.2
## 17 18 0.11136996 170086.8
## 18 19 0.10923424 168883.9
## 19 20 0.07690475 166774.2
## 20 21 0.10789721 165953.2
## 21 22 0.07446697 163961.4
## 22 23 0.10681950 164019.1
## 23 24 0.10456540 162800.9
## 24 25 0.07005768 160525.0
## 25 26 0.10415402 160761.5
## 26 27 0.07043926 158572.2
## 27 28 0.07159053 157657.1
## 28 29 0.10089204 158321.0
## 29 30 0.06590925 156443.8

Volem provar si obtenim millors resultats amb una versió reduïda del data frame. Aquest cop, extraiem les característiques dels jugadors que no tenen a veure clarament amb el seu estil de joc. Suprimim les variables overall_rating, potential, birth_year, birth_month, bmi i age:

remove_attr <- c(
  "overall_rating",
  "potential",
  "birth_year",
  "birth_month",
  "bmi",
  "age"
)
df_player_clustering_rd <- select(df_player_clustering, !all_of(remove_attr))
d <- daisy(df_player_clustering_rd)
k_values <- 2:30
results1_2 <- data.frame(k = k_values,
                      silhouette = double(length(k_values)),
                      withinss = double(length(k_values)))

for (i in k_values) {
  fit        <- kmeans(df_player_clustering_rd, i)
  y_cluster  <- fit$cluster
  sk         <- silhouette(y_cluster, dist(df_player_clustering_rd))
  results1_2[i - 1, 2] <- mean(sk[, 3])
  results1_2[i - 1, 3] <- fit$tot.withinss
}

results1_2
##     k silhouette withinss
## 1   2 0.51539822 250851.0
## 2   3 0.22922282 199966.9
## 3   4 0.19509164 177532.1
## 4   5 0.18804767 165806.1
## 5   6 0.17679831 158414.7
## 6   7 0.15420882 151544.4
## 7   8 0.14909339 147077.8
## 8   9 0.14755438 143545.0
## 9  10 0.14311977 140691.7
## 10 11 0.13704085 138140.3
## 11 12 0.12999790 136427.9
## 12 13 0.09161779 135807.9
## 13 14 0.09311572 136153.3
## 14 15 0.12868813 129907.2
## 15 16 0.08835007 129538.5
## 16 17 0.08525006 126904.5
## 17 18 0.08542205 125257.2
## 18 19 0.12192329 124710.2
## 19 20 0.08321761 123081.4
## 20 21 0.12172604 122713.7
## 21 22 0.07951338 123997.8
## 22 23 0.11831074 120120.6
## 23 24 0.08016209 119217.8
## 24 25 0.07729607 118473.5
## 25 26 0.07882176 117236.3
## 26 27 0.07814502 116485.2
## 27 28 0.07615547 117410.8
## 28 29 0.07689352 115588.9
## 29 30 0.07939160 114138.5

Els resultats són un pèl millors, així que ens quedem amb aquest data frame.

Veiem que amb la mesura Silhouette el millor resultat l’ha obtingut el clustering amb 2 grups, sembla lògic perquè a simple vista ja es veuen fàcilment dos grups. L’agrupació amb 3 grups ha sigut la segona que ha donat més bon resultat, però té un resultat bastant pobre en comparació amb els 2 grups.

Pel que fa a la mesura Withinss, la millor k s’acostuma a escollir en el punt en què la corba de la següent gràfica es comença a estabilitzar. En aquest cas seria 14.

plot(results1_2$k, results1_2$withinss,
    type = "o", col = "blue", pch = 0,
    xlab = "Nombre de clústers", ylab = "Withinss")

Una altra forma de trobar la millor k és utilitzar la funció kmeansruns del paquet fpc que executa l’algorisme K-means com un conjunt de valors i selecciona el nombre de clústers que millor funcioni d’acord amb la silueta mitjana (asw) i Calinski-Harabasz (ch).

fit_ch  <- kmeansruns(df_player_clustering_rd,
                      krange = k_values,
                      criterion = "ch")
fit_asw <- kmeansruns(df_player_clustering_rd,
                      krange = k_values,
                      criterion = "asw")

print(fit_ch$bestk)
## [1] 2
print(fit_asw$bestk)
## [1] 2

El valor obtingut amb tots dos casos és 2.

Per finalitzar, mostrem la gràfica clusplot amb catorze agrupacions:

player_kmeans14 <- kmeans(df_player_clustering_rd, 14)
y_cluster14 <- player_kmeans14$cluster
clusplot(df_player_clustering_rd, y_cluster14,
         color = TRUE, shade = TRUE, labels = 4, lines = 0)

2.1.3 Resultats

Hem pogut observar que generalment el millor nombre d’agrupacions per a dividir els jugadors basant-nos en les seues característiques és dos. Ja hem comentat diverses vegades que es deu a la clara diferència que hi ha entre els jugadors de camp i els porters.

També hem pogut veure que amb la mesura Withinss el millor nombre d’agrupacions és al voltant de catorze. Això, probablement es deu al fet que hi ha molts tipus de jugadors i, per tant, costa molt classificar-los en poques categories. Hem de pensar que hi ha 10 jugadors al camp, que desenvolupen un rol diferent. I a més els suplents, poden tenir altres funcions. En conseqüència, si a tot això li sumem el fet que certs jugadors amb característiques similars fan tasques diferents, ens trobem que costa molt de separar en clústers.

2.2 K-means amb una mètrica de distància diferent

En aquest apartat aprofundim amb els algorismes de clustering. Aquest cop farem servir una mètrica de distància diferent i comparem els resultats obtinguts amb el model anterior.

Per defecte, la funció kmeans(...) de la llibreria stats fa servir l’algorisme Hartigan and Wong (1978) per defecte amb la mètrica de distància euclidiana. En aquest cas, provarem amb la distància Manhattan.

2.2.1 Generació del model

Comencem a generar el model. Aquest cop no ens serveix la funció kmeans(...) de la llibreria stats per què no ens permet canviar de mètrica de distància.

Fem servir la funció hclust(...) de la llibreria stat. Aquesta funció s’utilitza per a generar un agrupament jeràrquic. L’agrupament jeràrquic és un mètode d’agrupament de punts de dades en grups (o clústers) basats en la seva similitud. Aquesta funció crea un arbre anomenat dendrograma, que mostra les relacions entre els punts de dades i els clústers als quals pertanyen.

Primer, calculem la matriu de distàncies de les dades dels jugadors utilitzant la funció dist(...) amb la mètrica de distància manhattan. Després apliquem el model amb el mètode ward.D, que és un mètode que intenta minimitzar la variància entre les distàncies dels punts d’una agrupació a una altra:

d_player_mh <- dist(df_player_clustering_rd, method = "manhattan")

player_mh <- hclust(d_player_mh, method = "ward.D")
plot(player_mh)

Podem observar que s’han generat dos grups principals ben diferenciats, però un d’ells conté moltes més subdivisions que l’altre. Podem deduir fàcilment que l’agrupament de més a l’esquerra és el dels porters i, l’altre és el dels jugadors de camp.

2.2.2 Anàlisi de la qualitat

Tal com hem fet anteriorment, calculem la silhouette amb diversos valors de k per a conèixer quin és el nombre òptim d’agrupacions.

Primer de tot, calculem el punt de tall per obtenir diferents clústers. Utilitzem la funció cutree(...) per obtenir diferents clústers a partir del resultat de hclust(...): Després, calculem el punt de silueta per cada nombre de grups:

k_values = 2:30

results_mh <- data.frame(k = k_values,
                        silhouette = double(length(k_values)))

for (i in k_values) {
  y_cluster  <- cutree(player_mh, k = i)
  sk         <- silhouette(y_cluster, d_player_mh)
  results_mh[i - 1, 2] <- mean(sk[, 3])
}

results_mh
##     k silhouette
## 1   2 0.57240484
## 2   3 0.23587290
## 3   4 0.19475414
## 4   5 0.16340985
## 5   6 0.15269248
## 6   7 0.14387257
## 7   8 0.13228295
## 8   9 0.12563787
## 9  10 0.11425126
## 10 11 0.10882982
## 11 12 0.06986312
## 12 13 0.06768430
## 13 14 0.06646533
## 14 15 0.05921678
## 15 16 0.05752081
## 16 17 0.05865500
## 17 18 0.05847554
## 18 19 0.05613726
## 19 20 0.05817578
## 20 21 0.05366450
## 21 22 0.05554523
## 22 23 0.05668727
## 23 24 0.05472497
## 24 25 0.05206734
## 25 26 0.05019943
## 26 27 0.04256667
## 27 28 0.04121230
## 28 29 0.04057813
## 29 30 0.04020356

Veiem com la k amb puntuació més alta torna a ser 2. Això ens indica que la millor forma de dividir els jugadors amb les dades que tenim és fent servir dos grups.

2.2.3 Comparació dels models

Els resultats molt similars als obtinguts amb kmeans(...) amb la distància euclidiana. En tots dos casos hem trobat que la millor k era 2. La principal diferència que veiem entre tots dos mètodes és que el valor de la silhouette és més alt si fem servir hclust(...) amb distància manhattan.

2.2.4 Resultats

Per concloure aquest apartat hem de comentar que ens ha semblat bastant difícil. No ha sigut fàcil trobar funcions i paquets d’R que facin clustering amb l’opció de modificar la mètrica de distància.

La primera aproximació ha estat intentar modificar l’algorisme K-means, però utilitzant la distància manhattan. Això ho hem arribat a aconseguir amb la funció kcca(...), però després no hem sigut capaços ni de visualitzar els agrupaments ni de fer-li una anàlisi de la qualitat.

Finalment, hem decidit canviar d’algorisme no supervisat i fer servir la funció hclust(...) combinada amb la distància manhattan. Aquesta funció ens permet canviar fàcilment de mètrica de distància, però aquesta solució no ens ha acabat de convèncer per què no estem fent servir el mateix algorisme que en la primera secció.

Malgrat tot, hem aconseguit fer una agrupació amb millor resultat que la del primer model. Per tant, valorem positivament el fet d’haver utilitzat hclust(...).

Per acabar, cal reiterar que sembla que amb les dades disponibles és complicat agrupar els jugadors. Un dels problemes que ja hem comentat és que cada jugador pot jugar a diverses posicions i que jugadors amb característiques similars juguin en posicions diferents.

També podria ser que el fet d’estar avaluant a tots els jugadors alhora pugui donar problemes. És a dir, els jugadors que són dolents tindran valoracions molt baixes en tots els camps. Això vol dir que, un davanter dolent no tindrà gaires diferències de puntuació entre les habilitats que se li pressuposen a un atacant entre les que ha de tenir un defensor. En canvi, un jugador molt bo, tindrà aquestes diferències molt marcades. En definitiva, creiem que seria interessant repetir aquest experiment filtrant per nivell dels jugadors.

2.3 DBSCAN i OPTICS

En aquest apartat seguim amb els models no supervisats. Però, aquest cop, fem servir dos algorismes de clustering basats en la densitat. Aquests s’especialitzen a identificar zones d’alta concentració d’observacions separades entre si per zones amb una menor densitat d’observacions.

2.3.1 Aplicació dels algorismes

2.3.1.1 DBSCAN

El primer algorisme que apliquem és DBSCAN. Aquest necessita inicialment dos paràmetres:

  • L’èpsilon \(\epsilon\): màxim veïnatge.
  • Punts mínims minPts: Nombre mínim de punts a l’\(\epsilon\)-veïnatge d’un punt.

DBSCAN construeix esferes de radi \(\epsilon\) que incloguin almenys minPts punts.

Primer de tot, carreguem els paquets necessaris:

packages <- c("dbscan")

not_installed <- packages[!(packages %in% installed.packages())]
if (length(not_installed) > 0) {
  install.packages(not_installed, repos = "http:/cran.us.r-project.org")
}
lapply(packages, library, character.only = TRUE)
## [[1]]
##  [1] "dbscan"     "fpc"        "cluster"    "corrplot"   "factoextra"
##  [6] "xfun"       "dplyr"      "Rmisc"      "plyr"       "lattice"   
## [11] "ggplot2"    "RSQLite"    "stats"      "graphics"   "grDevices" 
## [16] "utils"      "datasets"   "methods"    "base"

Ara realitzem una cerca per obtenir els paràmetres que ens donin el millor model (tenint en compte el mètode Silhouette):

d <- daisy(df_player_clustering_rd)

eps_vec <- c(2, 2, 2, 2, 2,
            5, 5, 5, 5, 5,
            7, 7, 7, 7, 7,
            10, 10, 10, 10, 10,
            15, 15, 15, 15, 15,
            20, 20, 20, 20, 20,
            50, 50, 50, 50, 50)
min_pts_vec <- c(2, 3, 5, 10, 15, 20, 100,
                2, 3, 5, 10, 15, 20, 100,
                2, 3, 5, 10, 15, 20, 100,
                2, 3, 5, 10, 15, 20, 100,
                2, 3, 5, 10, 15, 20, 100)
results_dbscan <- data.frame(eps = eps_vec,
                             minPts = min_pts_vec,
                             silhouette = double(length(eps_vec)),
                             clusters = integer(length(eps_vec)))

for (i in 1:nrow(results_dbscan)) {
  res <- dbscan(df_player_clustering_rd,
                eps = eps_vec[i],
                minPts = min_pts_vec[i])
  sk <- silhouette(res$cluster, d)
  value <- tryCatch(
        {
          val <- sk[, 3]
          mean(val)
        },
        error = function(cond) {
          return(NA)
        })
  results_dbscan[i, ]$silhouette <- value
  results_dbscan[i, ]$clusters <- length(unique(res$cluster))
}

results_dbscan[order(results_dbscan$silhouette, decreasing = TRUE), ]
##    eps minPts silhouette clusters
## 11   7     10  0.5153982        2
## 12   7     15  0.5153982        2
## 13   7     20  0.5153982        2
## 14   7    100  0.5153982        2
## 15   7      2  0.5153982        2
## 6    5     20  0.3569387        3
## 8    5      2  0.3569387        3
## 9    5      3  0.3569387        3
## 10   5      5  0.3569387        3
## 7    5    100  0.3292062        3
## 3    2      5 -0.3223893        6
## 2    2      3 -0.4715452       54
## 1    2      2 -0.5132098      215
## 4    2     10         NA        1
## 5    2     15         NA        1
## 16  10      3         NA        1
## 17  10      5         NA        1
## 18  10     10         NA        1
## 19  10     15         NA        1
## 20  10     20         NA        1
## 21  15    100         NA        1
## 22  15      2         NA        1
## 23  15      3         NA        1
## 24  15      5         NA        1
## 25  15     10         NA        1
## 26  20     15         NA        1
## 27  20     20         NA        1
## 28  20    100         NA        1
## 29  20      2         NA        1
## 30  20      3         NA        1
## 31  50      5         NA        1
## 32  50     10         NA        1
## 33  50     15         NA        1
## 34  50     20         NA        1
## 35  50    100         NA        1
results_dbscan <- na.omit(results_dbscan)
max_dbscan_eps <- results_dbscan[which.max(results_dbscan$silhouette), 1]
max_dbscan_pts <- results_dbscan[which.max(results_dbscan$silhouette), 2]
max_dbscan_sk <-  max(results_dbscan$silhouette)

Podem observar que una de les millors combinacions és fent servir eps = 7 i minPts = 10, que obtenen una Silhouette de 0.5153982. Aquest resultat podria ser millor, això significa que l’agrupació no és de tot bona. Veiem també que els millors models fan la separació amb dos grups, i que els segons millors models ho fan amb tres agrupacions.

A continuació, ens guardem un dels models amb millor resultat:

players_dbscan <- dbscan(df_player_clustering_rd,
                         eps = max_dbscan_eps,
                         minPts = max_dbscan_pts)
players_dbscan
## DBSCAN clustering for 9902 objects.
## Parameters: eps = 7, minPts = 10
## Using euclidean distances and borderpoints = TRUE
## The clustering contains 2 cluster(s) and 0 noise points.
## 
##    1    2 
## 9034  868 
## 
## Available fields: cluster, eps, minPts, dist, borderPoints

2.3.1.2 OPTICS

L’algorisme OPTICS serveix per trobar clústers basats en la densitat en dades espacials. La idea bàsica és similar a DBSCAN, però soluciona un dels seus principals problemes, que és haver de detectar agrupacions en dades amb densitat variable. Es pot entendre com a una generalització de DBSCAN.

El que fa és assignar una distància d’assolibilitat a cada punt del dataset. Necessita un radi \(\epsilon\) i un criteri de densitat minPts, però a diferència de DBSCAN el valor del radi no determina la formació de clústers, sinó que serveix per reduir la complexitat de càlcul.

players_optics <- optics(df_player_clustering_rd)
players_optics
## OPTICS ordering/clustering for 9902 objects.
## Parameters: minPts = 5, eps = 6.06827883934815, eps_cl = NA, xi = NA
## Available fields: order, reachdist, coredist, predecessor, minPts, eps,
##                   eps_cl, xi

També es poden extraure agrupacions similars a DBSCAN. La funció optics(...) admet que se li especifiqui el paràmetre eps_cl, que és el llindar per identificar els clústers (eps_cl \(\leq\) eps).

eps_vec <- c(.1, 10, 40, 80, 100, 300, 1000, 
             5000, 10000, 50000, 100000, 1000000)
results_optics <- data.frame(eps_cl = eps_vec,
                             silhouette = double(length(eps_vec)),
                             clusters = integer(length(eps_vec)))

for (i in 1:length(eps_vec)) {
  res <- optics(df_player_clustering_rd)
  res <- extractDBSCAN(res, eps_cl = eps_vec[i])
  sk <- silhouette(res$cluster, d)
  value <- tryCatch(
        {
          val <- sk[, 3]
          mean(val)
        },
        error = function(cond) {
          return(NA)
        })
  results_optics[i, ]$silhouette <- value
  results_optics[i, ]$clusters <- length(unique(res$cluster))
}

results_optics[order(results_optics$silhouette, decreasing = TRUE), ]
##    eps_cl silhouette clusters
## 2   1e+01  0.5153982        2
## 3   4e+01  0.5153982        2
## 4   8e+01  0.5153982        2
## 5   1e+02  0.5153982        2
## 6   3e+02  0.5153982        2
## 7   1e+03  0.5153982        2
## 8   5e+03  0.5153982        2
## 9   1e+04  0.5153982        2
## 10  5e+04  0.5153982        2
## 11  1e+05  0.5153982        2
## 12  1e+06  0.5153982        2
## 1   1e-01         NA        1
results_optics <- na.omit(results_optics)
max_optics_eps <- results_optics[which.max(results_optics$silhouette), 1]
max_optics_clt <- results_optics[which.max(results_optics$silhouette), 2]
max_optics_sk <-  max(results_optics$silhouette)

Podem comprovar que els resultats són molt similars als de DBSCAN. En tots dos casos la millor Silhouette s’assoleix quan es fan servir dos clústers, a més el seu valor és igual 0.5153982. En la majoria de proves s’ha aconseguit el millor model, un dels valors de eps_cl amb els quals s’ha obtingut la màxima puntuació és 10.

Ens guardem un dels millors models:

res <- optics(df_player_clustering_rd)
players_optics <- extractDBSCAN(res, eps_cl = max_optics_eps)
players_optics
## OPTICS ordering/clustering for 9902 objects.
## Parameters: minPts = 5, eps = 6.06827883934815, eps_cl = 10, xi = NA
## The clustering contains 2 cluster(s) and 0 noise points.
## 
##    1    2 
## 9034  868 
## 
## Available fields: order, reachdist, coredist, predecessor, minPts, eps,
##                   eps_cl, xi, cluster

A continuació visualitzem el model creat. Un diagrama d’accessibilitat o reachability plot és un gràfic que mostra de forma visual la distància d’accessibilitat de cada punt. Les valls representen clústers i els cims indiquen els punts que estan entre les agrupacions. Com més profunda és la vall, més dens és el clúster. Els punts entre agrupacions possiblement són outliers.

plot(players_optics)

2.3.2 Resultats

Els models obtinguts amb aquests algorismes són iguals als generats amb l’algorisme \(K\)-means. A més, els resultats obtinguts no són gaire satisfactoris perquè només aconseguim dividir els jugadors en dos categories, els porters i els jugadors de camp. És probable que amb les dades que tenim actualment no sigui fàcil diferenciar clarament entre els tipus de jugadors de camp.

2.4 Model de generació de regles a partir d’un arbre de decisió Quinlan C5.0

2.4.1 Preparació de les dades

En l’exercici anterior no s’havia tingut en compte aquest cas d’ús. Per això, ara cal preparar les dades adequadament per a poder implementar-ho.

El primer que fem és seleccionar els equips de forma única. És a dir, no volem un registre per a cada any, sinó un únic registre per a cada equip:

df_team_attributes$date <- as.Date(df_team_attributes$date, "%Y-%m-%d")

# https://stackoverflow.com/questions/30058708/select-row-with-most-recent-date-by-group
df_team_tree <-
  group_by(df_team_attributes, team_api_id) %>%
  slice(which.max(date))
df_team_tree <- as.data.frame(df_team_tree)

Després calculem el nombre de partits i victòries de cada equip i ho guardem al data frame df_team_tree:

calculate_home_games <- function(team_id, df_matches) {
  return(length(which(df_matches$home_team_api_id == team_id)))
}

calculate_away_games <- function(team_id, df_matches) {
  return(length(which(df_matches$away_team_api_id == team_id)))
}

calculate_home_wins <- function(team_id, df_matches) {
  df_home_wins <- which(df_matches$home_team_api_id == team_id & 
                        df_matches$home_team_goal > df_matches$home_team_goal)
  home_wins <- length(df_home_wins)
  return(home_wins)
}

calculate_away_wins <- function(team_id, df_matches) {
  df_away_wins <- which(df_matches$away_team_api_id == team_id & 
                        df_matches$away_team_goal > df_matches$home_team_goal)
  away_wins <- length(df_away_wins)
  return(away_wins)
}

df_team_tree$home_matches <- sapply(df_team_tree$team_api_id,
                               function(i) calculate_home_games(i, df_match))
df_team_tree$away_matches <- sapply(df_team_tree$team_api_id,
                               function(i) calculate_away_games(i, df_match))
df_team_tree$matches <- df_team_tree$home_matches + df_team_tree$away_matches

df_team_tree$home_wins <- sapply(df_team_tree$team_api_id,
                               function(i) calculate_home_wins(i, df_match))
df_team_tree$home_wins_p <- df_team_tree$home_wins / df_team_tree$home_matches
df_team_tree$away_wins <- sapply(df_team_tree$team_api_id,
                               function(i) calculate_away_wins(i, df_match))
df_team_tree$away_wins_p <- df_team_tree$away_wins / df_team_tree$away_matches
df_team_tree$wins <- df_team_tree$home_wins + df_team_tree$away_wins

Cada equip consta de molts atributs, alguns d’ells són similars. Seleccionem els que considerem rellevants i treiem la resta. Alguns d’ells són identificadors que no aporten res al model i la resta són les variables numèriques. Aquestes les traiem per què amb les variables categòriques contenen la mateixa informació de forma simplificada:

keep_attr <- c(
  "team_api_id",
  "buildUpPlaySpeedClass",
  "buildUpPlayDribblingClass",
  "buildUpPlayPassingClass",
  "buildUpPlayPositioningClass",
  "chanceCreationPassingClass",
  "chanceCreationCrossingClass",
  "chanceCreationShootingClass",
  "chanceCreationPositioningClass",
  "defencePressureClass",
  "defenceAggressionClass",
  "defenceTeamWidthClass",
  "defenceDefenderLineClass",
  "matches",
  "home_wins_p",
  "away_wins_p",
  "wins"
)
df_team_tree <- select(df_team_tree, all_of(keep_attr))

També cal crear la variable booleana local_win:

df_match$local_win <- df_match$home_team_goal > df_match$away_team_goal

Com que els jugadors que han participat en cada partit no ens interessen, esborrem les columnes que contenen els seus identificadors:

keep_attr <- c(
  "id",
  "league_id",
  "season",
  "stage",
  "home_team_api_id",
  "away_team_api_id",
  "local_win"
)
df_match_tree <- select(df_match, all_of(keep_attr))

Ara ja podem fer una unió de les característiques de cada equip amb el data frame match:

df_match_tree <- merge(x = df_match_tree, y = df_team_tree,
                       by.x = "home_team_api_id", by.y = "team_api_id")
# Rename new columns to add the prefix "home_team_"
names(df_match_tree) <- c("home_team_api_id",
                          "id",
                          "league_id",
                          "season",
                          "stage",
                          "away_team_api_id",
                          "local_win",
                          "home_team_buildUpPlaySpeedClass",
                          "home_team_buildUpPlayDribblingClass",
                          "home_team_buildUpPlayPassingClass",
                          "home_team_buildUpPlayPositioningClass",
                          "home_team_chanceCreationPassingClass",
                          "home_team_chanceCreationCrossingClass",
                          "home_team_chanceCreationShootingClass",
                          "home_team_chanceCreationPositioningClass",
                          "home_team_defencePressureClass",
                          "home_team_defenceAggressionClass",
                          "home_team_defenceTeamWidthClass",
                          "home_team_defenceDefenderLineClass",
                          "home_team_matches",
                          "home_team_home_wins_p",
                          "home_team_away_wins_p",
                          "home_team_wins")

df_match_tree <- merge(x = df_match_tree, y = df_team_tree,
                       by.x = "away_team_api_id", by.y = "team_api_id")
# Rename new columns to add the prefix "away_team_"
names(df_match_tree) <- c("home_team_api_id",
                          "id",
                          "league_id",
                          "season",
                          "stage",
                          "away_team_api_id",
                          "local_win",
                          "home_team_buildUpPlaySpeedClass",
                          "home_team_buildUpPlayDribblingClass",
                          "home_team_buildUpPlayPassingClass",
                          "home_team_buildUpPlayPositioningClass",
                          "home_team_chanceCreationPassingClass",
                          "home_team_chanceCreationCrossingClass",
                          "home_team_chanceCreationShootingClass",
                          "home_team_chanceCreationPositioningClass",
                          "home_team_defencePressureClass",
                          "home_team_defenceAggressionClass",
                          "home_team_defenceTeamWidthClass",
                          "home_team_defenceDefenderLineClass",
                          "home_team_matches",
                          "home_team_home_wins_p",
                          "home_team_away_wins_p",
                          "home_team_wins",
                          "away_team_buildUpPlaySpeedClass",
                          "away_team_buildUpPlayDribblingClass",
                          "away_team_buildUpPlayPassingClass",
                          "away_team_buildUpPlayPositioningClass",
                          "away_team_chanceCreationPassingClass",
                          "away_team_chanceCreationCrossingClass",
                          "away_team_chanceCreationShootingClass",
                          "away_team_chanceCreationPositioningClass",
                          "away_team_defencePressureClass",
                          "away_team_defenceAggressionClass",
                          "away_team_defenceTeamWidthClass",
                          "away_team_defenceDefenderLineClass",
                          "away_team_matches",
                          "away_team_home_wins_p",
                          "away_team_away_wins_p",
                          "away_team_wins")

Per poder utilitzar el data frame amb l’arbre C5.0 convertim totes les variables categòriques a factors:

df_match_tree[sapply(df_match_tree, is.character)] <- lapply(
  df_match_tree[sapply(df_match_tree, is.character)],
  as.factor
)

df_match_tree$local_win <- as.factor(df_match_tree$local_win)
str(df_match_tree)
## 'data.frame':    25629 obs. of  39 variables:
##  $ home_team_api_id                        : int  1601 1601 1601 1601 1601 1601 1601 1601 1601 1601 ...
##  $ id                                      : int  8030 8028 2186 8673 2186 2183 8245 8030 8673 8030 ...
##  $ league_id                               : int  15803 17470 17415 16304 17187 16911 16769 17590 15909 16987 ...
##  $ season                                  : int  15722 15722 15722 15722 15722 15722 15722 15722 15722 15722 ...
##  $ stage                                   : Factor w/ 8 levels "2008/2009","2009/2010",..: 1 8 8 3 7 5 5 8 1 6 ...
##  $ away_team_api_id                        : int  19 17 10 20 12 8 19 30 30 17 ...
##  $ local_win                               : Factor w/ 2 levels "FALSE","TRUE": 2 2 2 1 2 2 2 2 2 1 ...
##  $ home_team_buildUpPlaySpeedClass         : Factor w/ 3 levels "Balanced","Fast",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ home_team_buildUpPlayDribblingClass     : Factor w/ 3 levels "Little","Lots",..: 3 3 3 3 3 1 2 3 3 3 ...
##  $ home_team_buildUpPlayPassingClass       : Factor w/ 3 levels "Long","Mixed",..: 2 1 2 2 2 2 2 2 2 2 ...
##  $ home_team_buildUpPlayPositioningClass   : Factor w/ 2 levels "Free Form","Organised": 1 2 2 2 2 2 2 1 2 1 ...
##  $ home_team_chanceCreationPassingClass    : Factor w/ 3 levels "Normal","Risky",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ home_team_chanceCreationCrossingClass   : Factor w/ 3 levels "Little","Lots",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ home_team_chanceCreationShootingClass   : Factor w/ 3 levels "Little","Lots",..: 3 3 1 3 1 3 3 3 3 3 ...
##  $ home_team_chanceCreationPositioningClass: Factor w/ 2 levels "Free Form","Organised": 2 2 2 1 2 2 2 2 1 2 ...
##  $ home_team_defencePressureClass          : Factor w/ 3 levels "Deep","High",..: 3 3 3 3 3 3 1 3 3 3 ...
##  $ home_team_defenceAggressionClass        : Factor w/ 3 levels "Contain","Double",..: 3 3 3 3 3 1 3 3 3 3 ...
##  $ home_team_defenceTeamWidthClass         : Factor w/ 3 levels "Narrow","Normal",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ home_team_defenceDefenderLineClass      : Factor w/ 2 levels "Cover","Offside Trap": 1 1 1 1 1 1 1 1 1 1 ...
##  $ home_team_matches                       : int  240 180 210 240 210 150 210 240 240 240 ...
##  $ home_team_home_wins_p                   : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ home_team_away_wins_p                   : num  0.258 0.3 0.181 0.467 0.181 ...
##  $ home_team_wins                          : int  31 27 19 56 19 26 26 31 56 31 ...
##  $ away_team_buildUpPlaySpeedClass         : Factor w/ 3 levels "Balanced","Fast",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ away_team_buildUpPlayDribblingClass     : Factor w/ 3 levels "Little","Lots",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ away_team_buildUpPlayPassingClass       : Factor w/ 3 levels "Long","Mixed",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ away_team_buildUpPlayPositioningClass   : Factor w/ 2 levels "Free Form","Organised": 2 2 2 2 2 2 2 2 2 2 ...
##  $ away_team_chanceCreationPassingClass    : Factor w/ 3 levels "Normal","Risky",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ away_team_chanceCreationCrossingClass   : Factor w/ 3 levels "Little","Lots",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ away_team_chanceCreationShootingClass   : Factor w/ 3 levels "Little","Lots",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ away_team_chanceCreationPositioningClass: Factor w/ 2 levels "Free Form","Organised": 2 2 2 2 2 2 2 2 2 2 ...
##  $ away_team_defencePressureClass          : Factor w/ 3 levels "Deep","High",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ away_team_defenceAggressionClass        : Factor w/ 3 levels "Contain","Double",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ away_team_defenceTeamWidthClass         : Factor w/ 3 levels "Narrow","Normal",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ away_team_defenceDefenderLineClass      : Factor w/ 2 levels "Cover","Offside Trap": 1 1 1 1 1 1 1 1 1 1 ...
##  $ away_team_matches                       : int  240 240 240 240 240 240 240 240 240 240 ...
##  $ away_team_home_wins_p                   : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ away_team_away_wins_p                   : num  0.317 0.317 0.317 0.317 0.317 ...
##  $ away_team_wins                          : int  38 38 38 38 38 38 38 38 38 38 ...

Finalment, generem els conjunts de dades d’entrenament i de proves per a poder entrenar i estudiar la qualitat de l’arbre creat:

y <- df_match_tree$local_win

# Remove target attribute, plus unnecessary attributes
remove_attr <- c("local_win", "home_team_api_id", "id", "season")
X <- select(df_match_tree, !all_of(remove_attr))

set.seed(1899)
split_prop <- 3

indexes <- sample(1:nrow(df_match_tree),
  size = floor(((split_prop - 1) / split_prop) * nrow(df_match_tree))
)

train_X <- X[indexes, ]
train_y <- y[indexes]
test_X <- X[-indexes, ]
test_y <- y[-indexes]

Ara podem procedir a entrenar el model.

2.4.2 Generació de l’arbre

El primer que hem de fer és carregar la llibreria C50 per a generar l’arbre i les llibreries grid i ggplot2 per a visualitzar-lo:

packages <- c("C50", "grid", "ggplot2")

not_installed <- packages[!(packages %in% installed.packages())]
if (length(not_installed) > 0) {
  install.packages(not_installed, repos = "http:/cran.us.r-project.org")
}
lapply(packages, library, character.only = TRUE)
## [[1]]
##  [1] "C50"        "dbscan"     "fpc"        "cluster"    "corrplot"  
##  [6] "factoextra" "xfun"       "dplyr"      "Rmisc"      "plyr"      
## [11] "lattice"    "ggplot2"    "RSQLite"    "stats"      "graphics"  
## [16] "grDevices"  "utils"      "datasets"   "methods"    "base"      
## 
## [[2]]
##  [1] "grid"       "C50"        "dbscan"     "fpc"        "cluster"   
##  [6] "corrplot"   "factoextra" "xfun"       "dplyr"      "Rmisc"     
## [11] "plyr"       "lattice"    "ggplot2"    "RSQLite"    "stats"     
## [16] "graphics"   "grDevices"  "utils"      "datasets"   "methods"   
## [21] "base"      
## 
## [[3]]
##  [1] "grid"       "C50"        "dbscan"     "fpc"        "cluster"   
##  [6] "corrplot"   "factoextra" "xfun"       "dplyr"      "Rmisc"     
## [11] "plyr"       "lattice"    "ggplot2"    "RSQLite"    "stats"     
## [16] "graphics"   "grDevices"  "utils"      "datasets"   "methods"   
## [21] "base"

Ara creem el model utilitzant les dades d’entrenament. Fem servir l’argument trials per a fer servir adaptative boosting, que consisteix a agregar les prediccions de múltiples predictors per a construir diversos arbres de decisió i que aquests decideixin quina és la millor classe per a cada registre:

c50_model <- C5.0(train_X, train_y, trials = 100)

Fem servir la funció summary(...) perquè ens retorni informació sobre el model que acabem de crear. Mostra la crida que l’ha creat, el nombre de registres i atributs que s’han fet servir, les regles dels diferents arbres que ha generat i fa una petita avaluació del model amb les dades d’entrenament.

summary(c50_model)
## 
## Call:
## C5.0.default(x = train_X, y = train_y, trials = 100)
## 
## 
## C5.0 [Release 2.07 GPL Edition]      Tue Jan 24 21:44:26 2023
## -------------------------------
## 
## Class specified by attribute `outcome'
## 
## Read 17086 cases (36 attributes) from undefined.data
## 
## -----  Trial 0:  -----
## 
## Decision tree:
## 
## home_team_away_wins_p > 0.368421:
## :...home_team_buildUpPlayPassingClass = Long: FALSE (13/2)
## :   home_team_buildUpPlayPassingClass in {Mixed,Short}:
## :   :...away_team_wins <= 41: TRUE (2537/681)
## :       away_team_wins > 41:
## :       :...home_team_away_wins_p > 0.5384616:
## :           :...away_team_away_wins_p <= 0.6447368: TRUE (225/49)
## :           :   away_team_away_wins_p > 0.6447368: FALSE (27/9)
## :           home_team_away_wins_p <= 0.5384616:
## :           :...away_team_away_wins_p > 0.5367647: FALSE (99/22)
## :               away_team_away_wins_p <= 0.5367647:
## :               :...stage in {2008/2009,2012/2013,
## :                   :         2015/2016}: FALSE (270/123)
## :                   stage in {2010/2011,2011/2012}: TRUE (173/75)
## :                   stage = 2009/2010: [S1]
## :                   stage = 2013/2014: [S2]
## :                   stage = 2014/2015: [S3]
## home_team_away_wins_p <= 0.368421:
## :...away_team_away_wins_p > 0.3464052: FALSE (3491/848)
##     away_team_away_wins_p <= 0.3464052:
##     :...away_team_away_wins_p > 0.2315789:
##         :...home_team_wins <= 41: FALSE (4059/1536)
##         :   home_team_wins > 41:
##         :   :...stage in {2010/2011,2013/2014,2014/2015}: TRUE (360/159)
##         :       stage = 2008/2009: [S4]
##         :       stage = 2011/2012:
##         :       :...away_team_defenceTeamWidthClass in {Narrow,
##         :       :   :                                   Wide}: TRUE (6)
##         :       :   away_team_defenceTeamWidthClass = Normal: FALSE (89/40)
##         :       stage = 2012/2013: [S5]
##         :       stage = 2015/2016: [S6]
##         :       stage = 2009/2010:
##         :       :...home_team_defencePressureClass in {Deep,
##         :           :                                  High}: TRUE (5)
##         :           home_team_defencePressureClass = Medium: [S7]
##         away_team_away_wins_p <= 0.2315789:
##         :...home_team_wins > 33: TRUE (1586/629)
##             home_team_wins <= 33:
##             :...away_team_away_wins_p <= 0.1555556: TRUE (805/352)
##                 away_team_away_wins_p > 0.1555556:
##                 :...away_team_buildUpPlaySpeedClass = Slow: TRUE (13/4)
##                     away_team_buildUpPlaySpeedClass = Fast: [S8]
##                     away_team_buildUpPlaySpeedClass = Balanced:
##                     :...home_team_chanceCreationShootingClass = Little: [S9]
##                         home_team_chanceCreationShootingClass = Lots: [S10]
##                         home_team_chanceCreationShootingClass = Normal: [S11]
## 
## SubTree [S1]
## 
## home_team_defenceDefenderLineClass = Cover: TRUE (82/39)
## home_team_defenceDefenderLineClass = Offside Trap: FALSE (5)
## 
## SubTree [S2]
## 
## home_team_buildUpPlaySpeedClass in {Balanced,Slow}: FALSE (69/25)
## home_team_buildUpPlaySpeedClass = Fast: TRUE (14/4)
## 
## SubTree [S3]
## 
## home_team_chanceCreationShootingClass = Little: TRUE (7)
## home_team_chanceCreationShootingClass in {Lots,Normal}:
## :...away_team_chanceCreationShootingClass in {Little,Lots}: FALSE (20/4)
##     away_team_chanceCreationShootingClass = Normal: TRUE (59/26)
## 
## SubTree [S4]
## 
## home_team_chanceCreationPassingClass = Normal: FALSE (71/32)
## home_team_chanceCreationPassingClass in {Risky,Safe}: TRUE (14/2)
## 
## SubTree [S5]
## 
## away_team_buildUpPlayDribblingClass in {Little,Lots}: TRUE (6)
## away_team_buildUpPlayDribblingClass = Normal: FALSE (109/49)
## 
## SubTree [S6]
## 
## home_team_chanceCreationCrossingClass in {Little,Normal}: FALSE (116/46)
## home_team_chanceCreationCrossingClass = Lots: TRUE (14/4)
## 
## SubTree [S7]
## 
## away_team_buildUpPlaySpeedClass = Balanced: FALSE (90/40)
## away_team_buildUpPlaySpeedClass in {Fast,Slow}: TRUE (6)
## 
## SubTree [S8]
## 
## away_team_defenceAggressionClass = Contain: TRUE (5/1)
## away_team_defenceAggressionClass = Double: FALSE (5)
## away_team_defenceAggressionClass = Press:
## :...home_team_chanceCreationPassingClass in {Normal,Safe}: FALSE (144/49)
##     home_team_chanceCreationPassingClass = Risky: TRUE (19/7)
## 
## SubTree [S9]
## 
## away_team_buildUpPlayPassingClass in {Long,Short}: FALSE (17/5)
## away_team_buildUpPlayPassingClass = Mixed: TRUE (152/65)
## 
## SubTree [S10]
## 
## away_team_chanceCreationCrossingClass in {Little,Lots}: FALSE (5)
## away_team_chanceCreationCrossingClass = Normal:
## :...away_team_api_id <= 35: TRUE (72/29)
##     away_team_api_id > 35: FALSE (8)
## 
## SubTree [S11]
## 
## home_team_chanceCreationPositioningClass = Free Form: TRUE (118/53)
## home_team_chanceCreationPositioningClass = Organised:
## :...home_team_matches <= 180: FALSE (966/388)
##     home_team_matches > 180:
##     :...stage = 2008/2009: TRUE (152/68)
##         stage in {2010/2011,2011/2012,2012/2013,2013/2014,
##         :         2014/2015}: FALSE (735/351)
##         stage = 2009/2010:
##         :...away_team_defenceTeamWidthClass in {Narrow,
##         :   :                                   Normal}: FALSE (131/56)
##         :   away_team_defenceTeamWidthClass = Wide: TRUE (4)
##         stage = 2015/2016:
##         :...away_team_defenceDefenderLineClass = Offside Trap: FALSE (5)
##             away_team_defenceDefenderLineClass = Cover:
##             :...home_team_buildUpPlaySpeedClass in {Fast,
##                 :                                   Slow}: TRUE (7)
##                 home_team_buildUpPlaySpeedClass = Balanced:
##                 :...away_team_matches <= 218: FALSE (56/13)
##                     away_team_matches > 218: TRUE (45/17)
## 
## -----  Trial 1:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.3636364: FALSE (3599.2/1177)
## away_team_away_wins_p <= 0.3636364:
## :...home_team_away_wins_p > 0.4055944: TRUE (2498.9/826.5)
##     home_team_away_wins_p <= 0.4055944:
##     :...home_team_wins <= 9:
##         :...away_team_matches > 238: FALSE (666.9/207.1)
##         :   away_team_matches <= 238:
##         :   :...away_team_buildUpPlayDribblingClass = Little: TRUE (133.3/54.8)
##         :       away_team_buildUpPlayDribblingClass in {Lots,
##         :                                               Normal}: FALSE (616.6/277.3)
##         home_team_wins > 9:
##         :...away_team_wins > 41: FALSE (1476.1/644.2)
##             away_team_wins <= 41:
##             :...home_team_away_wins_p > 0.3358209: TRUE (1030/436.3)
##                 home_team_away_wins_p <= 0.3358209:
##                 :...away_team_buildUpPlaySpeedClass = Fast: FALSE (452.7/209.3)
##                     away_team_buildUpPlaySpeedClass = Slow: TRUE (187.9/81.9)
##                     away_team_buildUpPlaySpeedClass = Balanced: [S1]
## 
## SubTree [S1]
## 
## home_team_chanceCreationCrossingClass in {Little,Lots}: TRUE (643.9/295)
## home_team_chanceCreationCrossingClass = Normal:
## :...home_team_buildUpPlayPassingClass = Long: FALSE (135.7/54.3)
##     home_team_buildUpPlayPassingClass = Short: TRUE (70.8/34.5)
##     home_team_buildUpPlayPassingClass = Mixed:
##     :...stage in {2009/2010,2010/2011,2013/2014,2014/2015}: TRUE (2823.4/1378)
##         stage in {2012/2013,2015/2016}: FALSE (1423.3/671.9)
##         stage = 2008/2009:
##         :...home_team_buildUpPlaySpeedClass in {Balanced,
##         :   :                                   Slow}: FALSE (599.8/286.5)
##         :   home_team_buildUpPlaySpeedClass = Fast: TRUE (24.1/6.3)
##         stage = 2011/2012:
##         :...home_team_defenceTeamWidthClass in {Narrow,
##             :                                   Normal}: FALSE (679.7/330.6)
##             home_team_defenceTeamWidthClass = Wide: TRUE (23.6/5.3)
## 
## -----  Trial 2:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.4528302: FALSE (1548.8/444.9)
## away_team_away_wins_p <= 0.4528302:
## :...home_team_wins > 69: TRUE (1202.2/362.7)
##     home_team_wins <= 69:
##     :...home_team_matches <= 210: FALSE (4862.7/2114.3)
##         home_team_matches > 210:
##         :...away_team_away_wins_p <= 0.1832061: TRUE (2011/859.9)
##             away_team_away_wins_p > 0.1832061:
##             :...home_team_buildUpPlayPassingClass in {Long,
##                 :                                     Short}: FALSE (533.2/237.2)
##                 home_team_buildUpPlayPassingClass = Mixed: [S1]
## 
## SubTree [S1]
## 
## away_team_buildUpPlayDribblingClass in {Little,Lots}: FALSE (843.5/387.7)
## away_team_buildUpPlayDribblingClass = Normal:
## :...stage in {2008/2009,2009/2010,2011/2012,2015/2016}: TRUE (2921.4/1373.4)
##     stage in {2010/2011,2013/2014,2014/2015}: FALSE (2334.9/1116.5)
##     stage = 2012/2013:
##     :...away_team_wins <= 58: TRUE (791.7/377.9)
##         away_team_wins > 58: FALSE (36.6/8.1)
## 
## -----  Trial 3:  -----
## 
## Decision tree:
## 
## home_team_away_wins_p > 0.493421: TRUE (1234.1/404.7)
## home_team_away_wins_p <= 0.493421:
## :...away_team_wins > 64: FALSE (1344.2/410.7)
##     away_team_wins <= 64:
##     :...away_team_away_wins_p > 0.2735849:
##         :...home_team_matches <= 111: FALSE (529.9/164)
##         :   home_team_matches > 111:
##         :   :...home_team_buildUpPlayPassingClass in {Long,
##         :       :                                     Short}: FALSE (352.4/138.9)
##         :       home_team_buildUpPlayPassingClass = Mixed: [S1]
##         away_team_away_wins_p <= 0.2735849:
##         :...home_team_wins > 46: TRUE (1823.4/777.5)
##             home_team_wins <= 46:
##             :...away_team_defenceAggressionClass = Contain: TRUE (328.5/145.1)
##                 away_team_defenceAggressionClass = Double: FALSE (37.7/15.4)
##                 away_team_defenceAggressionClass = Press:
##                 :...stage in {2008/2009,2013/2014}: TRUE (1683/820.8)
##                     stage in {2009/2010,2010/2011,2014/2015,
##                     :         2015/2016}: FALSE (3420.3/1656.4)
##                     stage = 2011/2012: [S2]
##                     stage = 2012/2013: [S3]
## 
## SubTree [S1]
## 
## home_team_chanceCreationPassingClass in {Normal,Safe}: FALSE (4157.6/1923)
## home_team_chanceCreationPassingClass = Risky: TRUE (409.4/188.6)
## 
## SubTree [S2]
## 
## home_team_buildUpPlayDribblingClass in {Little,Lots}: FALSE (115.6/48.6)
## home_team_buildUpPlayDribblingClass = Normal: TRUE (749.7/338)
## 
## SubTree [S3]
## 
## away_team_buildUpPlaySpeedClass = Balanced: TRUE (803.8/380.7)
## away_team_buildUpPlaySpeedClass in {Fast,Slow}: FALSE (96.6/35.1)
## 
## -----  Trial 4:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.4666667:
## :...home_team_wins <= 73: FALSE (1336.3/425.7)
## :   home_team_wins > 73: TRUE (70.4/29)
## away_team_away_wins_p <= 0.4666667:
## :...home_team_away_wins_p > 0.3358209: TRUE (4179.9/1804.2)
##     home_team_away_wins_p <= 0.3358209:
##     :...away_team_away_wins_p > 0.3464052: FALSE (1910.1/828.3)
##         away_team_away_wins_p <= 0.3464052:
##         :...away_team_matches <= 70: TRUE (579.8/253.3)
##             away_team_matches > 70:
##             :...stage in {2008/2009,2011/2012,2012/2013}: FALSE (3375.3/1632.4)
##                 stage = 2010/2011: TRUE (1132.2/545.2)
##                 stage = 2009/2010:
##                 :...home_team_buildUpPlaySpeedClass = Balanced: TRUE (1049.2/519.6)
##                 :   home_team_buildUpPlaySpeedClass in {Fast,
##                 :                                       Slow}: FALSE (115.9/40)
##                 stage = 2013/2014: [S1]
##                 stage = 2014/2015: [S2]
##                 stage = 2015/2016:
##                 :...home_team_defencePressureClass = Deep: FALSE (80.8/31.1)
##                     home_team_defencePressureClass in {High,
##                                                        Medium}: TRUE (980.8/470)
## 
## SubTree [S1]
## 
## home_team_buildUpPlayDribblingClass in {Little,Lots}: TRUE (106.9/40.4)
## home_team_buildUpPlayDribblingClass = Normal: FALSE (1027.8/464.7)
## 
## SubTree [S2]
## 
## away_team_chanceCreationCrossingClass in {Little,Lots}: FALSE (82.3/29.3)
## away_team_chanceCreationCrossingClass = Normal: TRUE (1058.4/494.1)
## 
## -----  Trial 5:  -----
## 
## Decision tree:
## 
## away_team_wins > 76: FALSE (692.3/208.5)
## away_team_wins <= 76:
## :...away_team_away_wins_p > 0.4210526:
##     :...home_team_away_wins_p <= 0.3636364: FALSE (964.3/371.5)
##     :   home_team_away_wins_p > 0.3636364: TRUE (263.7/119.4)
##     away_team_away_wins_p <= 0.4210526:
##     :...home_team_away_wins_p > 0.4485294: TRUE (1498.2/588.3)
##         home_team_away_wins_p <= 0.4485294:
##         :...home_team_buildUpPlayPassingClass = Long:
##             :...home_team_chanceCreationShootingClass = Little: TRUE (101.8/41.8)
##             :   home_team_chanceCreationShootingClass in {Lots,
##             :                                             Normal}: FALSE (370.1/152.2)
##             home_team_buildUpPlayPassingClass = Short:
##             :...home_team_buildUpPlayPositioningClass = Free Form: TRUE (73.8/26.3)
##             :   home_team_buildUpPlayPositioningClass = Organised: FALSE (402.3/185.4)
##             home_team_buildUpPlayPassingClass = Mixed:
##             :...away_team_chanceCreationCrossingClass in {Little,
##                 :                                         Lots}: FALSE (1365.7/656.9)
##                 away_team_chanceCreationCrossingClass = Normal:
##                 :...stage in {2008/2009,2011/2012,
##                     :         2012/2013}: TRUE (4172.6/2040.6)
##                     stage in {2009/2010,2013/2014,
##                     :         2015/2016}: FALSE (4227.3/2053)
##                     stage = 2010/2011:
##                     :...home_team_matches <= 190: FALSE (400.7/172)
##                     :   home_team_matches > 190: TRUE (1045.9/491.6)
##                     stage = 2014/2015: [S1]
## 
## SubTree [S1]
## 
## home_team_buildUpPlayDribblingClass in {Little,Lots}: FALSE (189/72.1)
## home_team_buildUpPlayDribblingClass = Normal: TRUE (1318.3/638.3)
## 
## -----  Trial 6:  -----
## 
## Decision tree:
## 
## home_team_away_wins_p > 0.5735294: TRUE (506.4/161.9)
## home_team_away_wins_p <= 0.5735294:
## :...away_team_away_wins_p > 0.5073529: FALSE (874.5/278.4)
##     away_team_away_wins_p <= 0.5073529:
##     :...home_team_wins <= 11:
##         :...away_team_wins <= 27: TRUE (1001.3/468.6)
##         :   away_team_wins > 27:
##         :   :...stage = 2013/2014: TRUE (107.7/46.9)
##         :       stage in {2008/2009,2009/2010,2010/2011,2011/2012,2012/2013,
##         :       :         2014/2015,2015/2016}:
##         :       :...home_team_matches <= 182: FALSE (1002.3/349.7)
##         :           home_team_matches > 182: TRUE (33.2/10.7)
##         home_team_wins > 11:
##         :...stage in {2008/2009,2013/2014,2014/2015}: TRUE (5009.6/2476.5)
##             stage = 2012/2013: FALSE (1781.2/848.4)
##             stage = 2009/2010:
##             :...away_team_buildUpPlayPassingClass in {Long,
##             :   :                                     Short}: TRUE (123.7/43.5)
##             :   away_team_buildUpPlayPassingClass = Mixed: FALSE (1541.6/761)
##             stage = 2011/2012:
##             :...away_team_defenceAggressionClass in {Contain,
##             :   :                                    Press}: TRUE (1640.7/807.8)
##             :   away_team_defenceAggressionClass = Double: FALSE (12.1)
##             stage = 2015/2016:
##             :...home_team_matches <= 182: TRUE (299/117.4)
##             :   home_team_matches > 182: FALSE (1439.1/713.9)
##             stage = 2010/2011:
##             :...away_team_defenceDefenderLineClass = Offside Trap: FALSE (48/16)
##                 away_team_defenceDefenderLineClass = Cover:
##                 :...home_team_buildUpPlayPassingClass = Long: TRUE (18.8/5.8)
##                     home_team_buildUpPlayPassingClass = Short: FALSE (97.2/33.5)
##                     home_team_buildUpPlayPassingClass = Mixed: [S1]
## 
## SubTree [S1]
## 
## away_team_buildUpPlayPositioningClass = Free Form: TRUE (72.2/23.9)
## away_team_buildUpPlayPositioningClass = Organised:
## :...home_team_chanceCreationPositioningClass = Free Form: TRUE (136.7/54.5)
##     home_team_chanceCreationPositioningClass = Organised:
##     :...away_team_chanceCreationShootingClass = Little: TRUE (101.7/40.5)
##         away_team_chanceCreationShootingClass in {Lots,
##                                                   Normal}: FALSE (1238.9/597.4)
## 
## -----  Trial 7:  -----
## 
## Decision tree:
## 
## away_team_wins > 76: FALSE (669.2/220.4)
## away_team_wins <= 76:
## :...home_team_away_wins_p > 0.5735294: TRUE (469.9/151.7)
##     home_team_away_wins_p <= 0.5735294:
##     :...home_team_wins <= 13:
##         :...away_team_wins <= 27: TRUE (1193.9/584.6)
##         :   away_team_wins > 27: FALSE (1399/549.5)
##         home_team_wins > 13:
##         :...stage in {2008/2009,2011/2012,2012/2013,2013/2014,
##             :         2014/2015}: FALSE (8336.4/4063.4)
##             stage = 2009/2010:
##             :...home_team_chanceCreationShootingClass in {Little,
##             :   :                                         Normal}: TRUE (1503.3/704.2)
##             :   home_team_chanceCreationShootingClass = Lots: FALSE (132.2/53)
##             stage = 2010/2011:
##             :...home_team_buildUpPlayPassingClass in {Long,
##             :   :                                     Mixed}: TRUE (1595.5/768.3)
##             :   home_team_buildUpPlayPassingClass = Short: FALSE (102.5/36.6)
##             stage = 2015/2016:
##             :...away_team_chanceCreationShootingClass = Little: FALSE (138/56.2)
##                 away_team_chanceCreationShootingClass in {Lots,
##                                                           Normal}: TRUE (1546.1/718.2)
## 
## -----  Trial 8:  -----
## 
## Decision tree:
## 
## home_team_wins > 75: TRUE (808.2/302.9)
## home_team_wins <= 75:
## :...away_team_away_wins_p > 0.5073529: FALSE (852.4/295)
##     away_team_away_wins_p <= 0.5073529:
##     :...home_team_wins <= 13: FALSE (2536.7/1140.8)
##         home_team_wins > 13:
##         :...home_team_chanceCreationShootingClass in {Little,
##             :                                         Normal}: TRUE (11971.4/5916)
##             home_team_chanceCreationShootingClass = Lots: FALSE (917.3/441.1)
## 
## -----  Trial 9:  -----
## 
## Decision tree:
## 
## home_team_wins > 75: TRUE (801.5/308.8)
## home_team_wins <= 75:
## :...away_team_wins <= 10: TRUE (2166.4/1029)
##     away_team_wins > 10: FALSE (14118.1/6688)
## 
## -----  Trial 10:  -----
## 
## Decision tree:
## 
## home_team_away_wins_p > 0.5241935: TRUE (896.5/356.2)
## home_team_away_wins_p <= 0.5241935:
## :...home_team_wins <= 42: FALSE (11798.8/5661.9)
##     home_team_wins > 42:
##     :...away_team_chanceCreationPositioningClass = Free Form: TRUE (478.9/209.8)
##         away_team_chanceCreationPositioningClass = Organised:
##         :...stage in {2008/2009,2009/2010,2012/2013,2013/2014,2014/2015,
##             :         2015/2016}: TRUE (2937.2/1375.4)
##             stage in {2010/2011,2011/2012}: FALSE (974.6/458.4)
## 
## -----  Trial 11:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.4666667: FALSE (1315.5/521.2)
## away_team_away_wins_p <= 0.4666667:
## :...away_team_defenceAggressionClass = Contain: TRUE (493.8/232.8)
##     away_team_defenceAggressionClass = Double: FALSE (128.7/57.7)
##     away_team_defenceAggressionClass = Press:
##     :...home_team_buildUpPlayPassingClass = Short: TRUE (731.7/342.1)
##         home_team_buildUpPlayPassingClass = Long:
##         :...home_team_chanceCreationShootingClass = Little: TRUE (109.8/44.8)
##         :   home_team_chanceCreationShootingClass in {Lots,
##         :                                             Normal}: FALSE (367.6/160.4)
##         home_team_buildUpPlayPassingClass = Mixed:
##         :...away_team_chanceCreationPassingClass in {Normal,
##             :                                        Risky}: TRUE (13572/6589.5)
##             away_team_chanceCreationPassingClass = Safe: FALSE (366.9/172.3)
## 
## -----  Trial 12:  -----
## 
## Decision tree:
## 
## home_team_away_wins_p > 0.6447368: TRUE (315.5/112.1)
## home_team_away_wins_p <= 0.6447368:
## :...away_team_wins > 41: FALSE (5159.1/2364.7)
##     away_team_wins <= 41:
##     :...stage in {2008/2009,2010/2011,2014/2015}: FALSE (4371/2160.1)
##         stage in {2009/2010,2011/2012,2012/2013,
##         :         2013/2014}: TRUE (5792.5/2795.8)
##         stage = 2015/2016:
##         :...home_team_buildUpPlaySpeedClass in {Balanced,
##             :                                   Slow}: TRUE (1348.1/667.4)
##             home_team_buildUpPlaySpeedClass = Fast: FALSE (99.9/39.8)
## 
## -----  Trial 13:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.56: FALSE (520.8/187.4)
## away_team_away_wins_p <= 0.56:
## :...home_team_away_wins_p > 0.5735294: TRUE (437.2/162.6)
##     home_team_away_wins_p <= 0.5735294:
##     :...home_team_matches <= 150: FALSE (2618.5/1221.5)
##         home_team_matches > 150:
##         :...away_team_buildUpPlaySpeedClass in {Fast,
##             :                                   Slow}: TRUE (1411.7/673.4)
##             away_team_buildUpPlaySpeedClass = Balanced:
##             :...away_team_buildUpPlayPassingClass in {Long,
##                 :                                     Short}: TRUE (918.6/438.2)
##                 away_team_buildUpPlayPassingClass = Mixed: FALSE (11179.2/5564.4)
## 
## -----  Trial 14:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.4666667: FALSE (1293.3/538.4)
## away_team_away_wins_p <= 0.4666667:
## :...home_team_away_wins_p > 0.5241935: TRUE (822.9/336.2)
##     home_team_away_wins_p <= 0.5241935:
##     :...stage in {2008/2009,2010/2011,2012/2013}: TRUE (5658.9/2763.1)
##         stage in {2009/2010,2011/2012,2015/2016}: FALSE (5590.7/2773.7)
##         stage = 2013/2014:
##         :...away_team_buildUpPlayDribblingClass in {Little,
##         :   :                                       Lots}: FALSE (226.5/95.6)
##         :   away_team_buildUpPlayDribblingClass = Normal: TRUE (1556.8/751.3)
##         stage = 2014/2015:
##         :...away_team_buildUpPlayDribblingClass = Little: FALSE (167.9/69.4)
##             away_team_buildUpPlayDribblingClass = Lots: TRUE (96.2/43.5)
##             away_team_buildUpPlayDribblingClass = Normal:
##             :...home_team_buildUpPlayPositioningClass = Free Form: TRUE (67.1/23.3)
##                 home_team_buildUpPlayPositioningClass = Organised: [S1]
## 
## SubTree [S1]
## 
## away_team_chanceCreationPositioningClass = Free Form: FALSE (137.9/54.1)
## away_team_chanceCreationPositioningClass = Organised:
## :...away_team_buildUpPlayPassingClass = Long: FALSE (62.7/23.1)
##     away_team_buildUpPlayPassingClass = Short: TRUE (33.9/15)
##     away_team_buildUpPlayPassingClass = Mixed:
##     :...home_team_away_wins_p <= 0.2222222: FALSE (463.1/205)
##         home_team_away_wins_p > 0.2222222: TRUE (908/407)
## 
## -----  Trial 15:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.56: FALSE (512.3/192.5)
## away_team_away_wins_p <= 0.56:
## :...home_team_buildUpPlayPositioningClass = Free Form: TRUE (769/344.9)
##     home_team_buildUpPlayPositioningClass = Organised:
##     :...home_team_wins > 63: TRUE (1498.2/683.5)
##         home_team_wins <= 63:
##         :...home_team_buildUpPlayDribblingClass = Lots: TRUE (541.9/270.6)
##             home_team_buildUpPlayDribblingClass = Little:
##             :...away_team_defencePressureClass in {Deep,
##             :   :                                  High}: TRUE (150.5/61.7)
##             :   away_team_defencePressureClass = Medium: FALSE (1372/660.6)
##             home_team_buildUpPlayDribblingClass = Normal:
##             :...home_team_chanceCreationCrossingClass in {Little,
##                 :                                         Lots}: TRUE (1236.3/600.2)
##                 home_team_chanceCreationCrossingClass = Normal:
##                 :...stage in {2008/2009,2009/2010,
##                     :         2011/2012}: TRUE (3879.1/1891.9)
##                     stage in {2010/2011,2012/2013,2013/2014,2014/2015,
##                               2015/2016}: FALSE (7126.7/3464.1)
## 
## -----  Trial 16:  -----
## 
## Decision tree:
## 
## away_team_wins > 73: FALSE (801.5/331.5)
## away_team_wins <= 73:
## :...home_team_away_wins_p > 0.6447368: TRUE (276.7/101.8)
##     home_team_away_wins_p <= 0.6447368:
##     :...away_team_away_wins_p <= 0.2516556:
##         :...home_team_wins > 65: TRUE (409.5/159.9)
##         :   home_team_wins <= 65:
##         :   :...stage in {2008/2009,2010/2011,2012/2013,2013/2014,
##         :       :         2015/2016}: TRUE (4550.3/2198.7)
##         :       stage = 2011/2012: FALSE (922.2/450.8)
##         :       stage = 2014/2015:
##         :       :...away_team_buildUpPlayPassingClass = Long: FALSE (40.5/11.4)
##         :       :   away_team_buildUpPlayPassingClass in {Mixed,
##         :       :                                         Short}: TRUE (919.2/446.5)
##         :       stage = 2009/2010:
##         :       :...home_team_buildUpPlaySpeedClass = Slow: FALSE (24/5.7)
##         :           home_team_buildUpPlaySpeedClass in {Balanced,Fast}: [S1]
##         away_team_away_wins_p > 0.2516556:
##         :...home_team_wins <= 3: FALSE (157.7/49.1)
##             home_team_wins > 3:
##             :...stage in {2013/2014,2015/2016}: FALSE (2110.9/1034.4)
##                 stage = 2014/2015: TRUE (1041/512.1)
##                 stage = 2009/2010:
##                 :...away_team_defenceAggressionClass = Contain: TRUE (20.9/4.4)
##                 :   away_team_defenceAggressionClass in {Double,
##                 :                                        Press}: FALSE (926.7/434.4)
##                 stage = 2011/2012:
##                 :...away_team_defenceAggressionClass in {Contain,
##                 :   :                                    Press}: TRUE (976.2/469.3)
##                 :   away_team_defenceAggressionClass = Double: FALSE (13.3/0.7)
##                 stage = 2012/2013:
##                 :...home_team_matches <= 272: FALSE (717.1/315.5)
##                 :   home_team_matches > 272: TRUE (309.2/133.1)
##                 stage = 2008/2009:
##                 :...away_team_defenceAggressionClass = Contain: FALSE (21.5/6.3)
##                 :   away_team_defenceAggressionClass = Double: TRUE (16.9/5.5)
##                 :   away_team_defenceAggressionClass = Press: [S2]
##                 stage = 2010/2011:
##                 :...home_team_buildUpPlayPassingClass in {Long,
##                     :                                     Short}: FALSE (72.4/23.4)
##                     home_team_buildUpPlayPassingClass = Mixed: [S3]
## 
## SubTree [S1]
## 
## away_team_defenceTeamWidthClass in {Narrow,Normal}: TRUE (914.5/421.1)
## away_team_defenceTeamWidthClass = Wide: FALSE (66.3/25.2)
## 
## SubTree [S2]
## 
## away_team_chanceCreationShootingClass in {Little,Normal}: FALSE (738.7/342.6)
## away_team_chanceCreationShootingClass = Lots: TRUE (125.2/47.5)
## 
## SubTree [S3]
## 
## away_team_chanceCreationPositioningClass = Free Form: TRUE (143.8/58.2)
## away_team_chanceCreationPositioningClass = Organised: FALSE (769.9/365.5)
## 
## -----  Trial 17:  -----
## 
## Decision tree:
## 
## away_team_away_wins_p > 0.56: FALSE (503/199.4)
## away_team_away_wins_p <= 0.56:
## :...home_team_wins > 69: TRUE (1131.3/499.5)
##     home_team_wins <= 69:
##     :...home_team_chanceCreationPassingClass = Risky: TRUE (1638.4/814.5)
##         home_team_chanceCreationPassingClass = Safe: FALSE (404.4/184.6)
##         home_team_chanceCreationPassingClass = Normal:
##         :...home_team_buildUpPlaySpeedClass = Fast: FALSE (394.4/178.4)
##             home_team_buildUpPlaySpeedClass = Slow: TRUE (282/132.4)
##             home_team_buildUpPlaySpeedClass = Balanced:
##             :...home_team_chanceCreationShootingClass = Little: TRUE (1076/515.3)
##                 home_team_chanceCreationShootingClass = Lots: FALSE (617.7/294.1)
##                 home_team_chanceCreationShootingClass = Normal:
##                 :...home_team_buildUpPlayPassingClass in {Long,
##                     :                                     Short}: FALSE (348.3/144.9)
##                     home_team_buildUpPlayPassingClass = Mixed:
##                     :...away_team_wins > 64: FALSE (490.7/207.2)
##                         away_team_wins <= 64: [S1]
## 
## SubTree [S1]
## 
## away_team_chanceCreationShootingClass = Little: TRUE (779.4/387.2)
## away_team_chanceCreationShootingClass = Lots:
## :...home_team_matches <= 96: FALSE (39.5/10.4)
## :   home_team_matches > 96: TRUE (472.1/212.2)
## away_team_chanceCreationShootingClass = Normal:
## :...stage in {2008/2009,2010/2011,2011/2012,2012/2013,
##     :         2014/2015}: FALSE (5629.6/2775.3)
##     stage = 2009/2010:
##     :...home_team_chanceCreationCrossingClass in {Little,
##     :   :                                         Normal}: TRUE (1033.2/488.7)
##     :   home_team_chanceCreationCrossingClass = Lots: FALSE (41.7/14.6)
##     stage = 2013/2014:
##     :...away_team_defenceDefenderLineClass = Offside Trap: FALSE (9/0.9)
##     :   away_team_defenceDefenderLineClass = Cover:
##     :   :...home_team_defenceAggressionClass = Contain: FALSE (24.4/6.8)
##     :       home_team_defenceAggressionClass in {Double,
##     :                                            Press}: TRUE (1042/505.6)
##     stage = 2015/2016:
##     :...home_team_chanceCreationCrossingClass = Little: FALSE (21.6/5.8)
##         home_team_chanceCreationCrossingClass = Lots: TRUE (19.3/6.9)
##         home_team_chanceCreationCrossingClass = Normal:
##         :...home_team_away_wins_p <= 0.1578947: FALSE (70.8/19.7)
##             home_team_away_wins_p > 0.1578947: TRUE (1017.2/481.6)
## 
## -----  Trial 18:  -----
## 
## Decision tree:
## 
## home_team_buildUpPlayPositioningClass = Free Form:
## :...home_team_matches <= 286: FALSE (562.3/276)
## :   home_team_matches > 286: TRUE (225.7/82)
## home_team_buildUpPlayPositioningClass = Organised:
## :...away_team_buildUpPlayPositioningClass = Free Form: FALSE (706/325.5)
##     away_team_buildUpPlayPositioningClass = Organised:
##     :...home_team_buildUpPlayPassingClass in {Long,
##         :                                     Short}: FALSE (1132.8/541.3)
##         home_team_buildUpPlayPassingClass = Mixed:
##         :...away_team_api_id <= 4: FALSE (1618.8/744.7)
##             away_team_api_id > 4:
##             :...away_team_defenceAggressionClass = Contain: TRUE (394.8/181.6)
##                 away_team_defenceAggressionClass = Double: FALSE (166.2/78.4)
##                 away_team_defenceAggressionClass = Press:
##                 :...home_team_away_wins_p <= 0.2315789: FALSE (4885.9/2350.3)
##                     home_team_away_wins_p > 0.2315789:
##                     :...stage in {2009/2010,2011/2012,2012/2013,
##                         :         2014/2015}: TRUE (3733.8/1776.1)
##                         stage = 2013/2014: FALSE (920.5/447.3)
##                         stage = 2008/2009: [S1]
##                         stage = 2010/2011: [S2]
##                         stage = 2015/2016: [S3]
## 
## SubTree [S1]
## 
## home_team_chanceCreationShootingClass in {Little,Lots}: FALSE (168.4/70.5)
## home_team_chanceCreationShootingClass = Normal: TRUE (596.6/284.3)
## 
## SubTree [S2]
## 
## home_team_chanceCreationShootingClass = Little: FALSE (89.5/37.8)
## home_team_chanceCreationShootingClass = Lots: TRUE (70.2/24)
## home_team_chanceCreationShootingClass = Normal:
## :...away_team_defenceDefenderLineClass = Cover: TRUE (739.3/352.8)
##     away_team_defenceDefenderLineClass = Offside Trap: FALSE (17.5/4.8)
## 
## SubTree [S3]
## 
## home_team_defenceDefenderLineClass = Offside Trap: TRUE (26.1/6.9)
## home_team_defenceDefenderLineClass = Cover:
## :...home_team_buildUpPlayDribblingClass in {Little,Normal}: TRUE (990.3/475.5)
##     home_team_buildUpPlayDribblingClass = Lots: FALSE (41.2/11.7)
## 
## -----  Trial 19:  -----
## 
## Decision tree:
## 
## home_team_buildUpPlayPositioningClass = Free Form: TRUE (788.4/362.7)
## home_team_buildUpPlayPositioningClass = Organised:
## :...away_team_wins > 58:
##     :...home_team_chanceCreationShootingClass in {Little,
##     :   :                                         Lots}: TRUE (312.8/137.5)
##     :   home_team_chanceCreationShootingClass = Normal: FALSE (1810.7/799)
##     away_team_wins <= 58:
##     :...home_team_chanceCreationCrossingClass = Little: TRUE (378.4/177.1)
##         home_team_chanceCreationCrossingClass = Lots: FALSE (1084.9/527.7)
##         home_team_chanceCreationCrossingClass = Normal:
##         :...away_team_chanceCreationPassingClass in {Normal,
##             :                                        Risky}: TRUE (12364.1/6123.9)
##             away_team_chanceCreationPassingClass = Safe: FALSE (346.6/155.3)
## 
## -----  Trial 20:  -----
## 
## Decision tree:
## 
## home_team_away_wins_p <= 0.5241935: FALSE (16223.1/7960.6)
## home_team_away_wins_p > 0.5241935: TRUE (862.9/384.1)
## 
## -----  Trial 21:  -----
## 
## Decision tree:
## 
## home_team_buildUpPlayPositioningClass = Free Form: TRUE (787.3/365.3)
## home_team_buildUpPlayPositioningClass = Organised: FALSE (16298.7/8108.1)
## 
## *** boosting reduced to 21 trials since last classifier is very inaccurate
## 
## 
## Evaluation on training data (17086 cases):
## 
## Trial        Decision Tree   
## -----      ----------------  
##    Size      Errors  
## 
##    0     50 5902(34.5%)
##    1     18 6408(37.5%)
##    2     10 6660(39.0%)
##    3     15 6522(38.2%)
##    4     15 6741(39.5%)
##    5     15 7164(41.9%)
##    6     21 7556(44.2%)
##    7     11 7567(44.3%)
##    8      5 7610(44.5%)
##    9      3 6867(40.2%)
##   10      5 6741(39.5%)
##   11      8 8043(47.1%)
##   12      6 7363(43.1%)
##   13      6 7598(44.5%)
##   14     14 7540(44.1%)
##   15      9 7260(42.5%)
##   16     26 7065(41.3%)
##   17     23 7461(43.7%)
##   18     19 7201(42.1%)
##   19      7 7975(46.7%)
##   20      2 7261(42.5%)
## boost           5877(34.4%)   <<
## 
## 
##     (a)   (b)    <-classified as
##    ----  ----
##    6922  2304    (a): class FALSE
##    3573  4287    (b): class TRUE
## 
## 
##  Attribute usage:
## 
##  100.00% home_team_buildUpPlayPositioningClass
##  100.00% home_team_away_wins_p
##  100.00% home_team_wins
##  100.00% away_team_away_wins_p
##  100.00% away_team_wins
##   98.40% home_team_buildUpPlayPassingClass
##   96.98% away_team_defenceAggressionClass
##   95.89% stage
##   95.27% away_team_buildUpPlayPositioningClass
##   94.97% home_team_matches
##   94.13% home_team_chanceCreationShootingClass
##   89.85% home_team_chanceCreationCrossingClass
##   88.77% home_team_chanceCreationPassingClass
##   87.34% away_team_chanceCreationPassingClass
##   83.57% away_team_api_id
##   83.53% home_team_buildUpPlayDribblingClass
##   83.44% away_team_buildUpPlaySpeedClass
##   79.60% home_team_buildUpPlaySpeedClass
##   72.49% away_team_chanceCreationCrossingClass
##   72.33% away_team_buildUpPlayPassingClass
##   65.98% away_team_chanceCreationShootingClass
##   54.18% away_team_buildUpPlayDribblingClass
##   54.18% away_team_matches
##   35.96% away_team_chanceCreationPositioningClass
##   20.12% home_team_chanceCreationPositioningClass
##   16.91% away_team_defenceDefenderLineClass
##    9.04% away_team_defencePressureClass
##    6.62% home_team_defencePressureClass
##    6.51% home_team_defenceDefenderLineClass
##    6.08% away_team_defenceTeamWidthClass
##    5.96% home_team_defenceAggressionClass
##    4.00% home_team_defenceTeamWidthClass
## 
## 
## Time: 1.6 secs

Ara podem visualitzar el model. Per a fer-ho, hem de treure l’argument rules:

c50_model <- C5.0(train_X, train_y, trials = 100)
plot(c50_model, gp = gpar(fontsize = 9.5))

Com que és un arbre amb moltes regles, es veu una imatge molt petita, però si hi cliquem amb el botó dret del ratolí i l’obrim en una pestanya nova es pot ampliar.

Una de les opcions és eliminar aquelles variables que aporten poc al model.

Per a fer-ho, utilitzem la funció anomenada C5imp(...) que mostra la importància de cada atribut segons la mètrica escollida. La mètrica usage es calcula la importància a partir del percentatge de mostres del conjunt d’entrenament que acaben a un node terminal després de la divisió. D’aquesta manera, tenim que la primera variable a separar el conjunt té un valor de 100. A partir, d’aquesta, la resta tenen valors més xics. la mètrica splits la importància es calcula a partir del percentatge de separacions associades a cada variable.

imp_usage <- C5imp(c50_model, metric = "usage")
imp_splits <- C5imp(c50_model, metric = "splits")

row_names <- sort(rownames(imp_usage))
imp_usage_sort <- imp_usage[order(rownames(imp_usage)), ]
imp_splits_sort <- imp_splits[order(rownames(imp_splits)), ]

df_imp <- data.frame(
  attribute = row_names,
  usage = imp_usage_sort,
  splits = imp_splits_sort
)
df_imp
##                                   attribute  usage    splits
## 1                          away_team_api_id  83.57 0.9803922
## 2                     away_team_away_wins_p 100.00 9.8039216
## 3       away_team_buildUpPlayDribblingClass  54.18 2.4509804
## 4         away_team_buildUpPlayPassingClass  72.33 2.4509804
## 5     away_team_buildUpPlayPositioningClass  95.27 0.9803922
## 6           away_team_buildUpPlaySpeedClass  83.44 2.4509804
## 7     away_team_chanceCreationCrossingClass  72.49 1.4705882
## 8      away_team_chanceCreationPassingClass  87.34 0.9803922
## 9  away_team_chanceCreationPositioningClass  35.96 1.4705882
## 10    away_team_chanceCreationShootingClass  65.98 2.4509804
## 11         away_team_defenceAggressionClass  96.98 3.9215686
## 12       away_team_defenceDefenderLineClass  16.91 1.9607843
## 13           away_team_defencePressureClass   9.04 0.4901961
## 14          away_team_defenceTeamWidthClass   6.08 1.4705882
## 15                    away_team_home_wins_p   0.00 0.0000000
## 16                        away_team_matches  54.18 1.4705882
## 17                           away_team_wins 100.00 6.3725490
## 18                    home_team_away_wins_p 100.00 9.3137255
## 19      home_team_buildUpPlayDribblingClass  83.53 2.4509804
## 20        home_team_buildUpPlayPassingClass  98.40 5.3921569
## 21    home_team_buildUpPlayPositioningClass 100.00 2.4509804
## 22          home_team_buildUpPlaySpeedClass  79.60 3.4313725
## 23    home_team_chanceCreationCrossingClass  89.85 2.9411765
## 24     home_team_chanceCreationPassingClass  88.77 1.9607843
## 25 home_team_chanceCreationPositioningClass  20.12 0.9803922
## 26    home_team_chanceCreationShootingClass  94.13 4.9019608
## 27         home_team_defenceAggressionClass   5.96 0.4901961
## 28       home_team_defenceDefenderLineClass   6.51 0.9803922
## 29           home_team_defencePressureClass   6.62 0.9803922
## 30          home_team_defenceTeamWidthClass   4.00 0.4901961
## 31                    home_team_home_wins_p   0.00 0.0000000
## 32                        home_team_matches  94.97 4.9019608
## 33                           home_team_wins 100.00 7.8431373
## 34                                league_id   0.00 0.0000000
## 35                                    stage  95.89 9.3137255

Fem servir la informació de les dos mètriques per a generar un conjunt dels atributs més rellevants:

usage_threshold <- 10
splits_threshold <- 6
most_imp <- df_imp$attribute[
  df_imp$usage > usage_threshold |
  df_imp$splits > splits_threshold
]
test_imp_X <- test_X[most_imp]
train_imp_X <- train_X[most_imp]

Ara procedim a entrenar i mostrar les regles del model:

c50_model_imp <- C5.0(train_imp_X, train_y, rules = TRUE, trials = 100)
summary(c50_model_imp)
## 
## Call:
## C5.0.default(x = train_imp_X, y = train_y, trials = 100, rules = TRUE)
## 
## 
## C5.0 [Release 2.07 GPL Edition]      Tue Jan 24 21:44:37 2023
## -------------------------------
## 
## Class specified by attribute `outcome'
## 
## Read 17086 cases (27 attributes) from undefined.data
## 
## -----  Trial 0:  -----
## 
## Rules:
## 
## Rule 0/1: (416/62, lift 1.6)
##  away_team_away_wins_p > 0.6447368
##  ->  class FALSE  [0.849]
## 
## Rule 0/2: (13/2, lift 1.5)
##  home_team_away_wins_p > 0.368421
##  home_team_buildUpPlayPassingClass = Long
##  ->  class FALSE  [0.800]
## 
## Rule 0/3: (53/12, lift 1.4)
##  away_team_buildUpPlayDribblingClass = Little
##  away_team_buildUpPlaySpeedClass = Balanced
##  home_team_chanceCreationShootingClass = Normal
##  home_team_wins <= 33
##  stage = 2013/2014
##  ->  class FALSE  [0.764]
## 
## Rule 0/4: (5368/1603, lift 1.3)
##  away_team_wins > 41
##  home_team_away_wins_p <= 0.5384616
##  ->  class FALSE  [0.701]
## 
## Rule 0/5: (13486/5452, lift 1.1)
##  home_team_away_wins_p <= 0.368421
##  ->  class FALSE  [0.596]
## 
## Rule 0/6: (10, lift 2.0)
##  away_team_away_wins_p > 0.1555556
##  away_team_away_wins_p <= 0.2315789
##  away_team_buildUpPlaySpeedClass = Balanced
##  away_team_chanceCreationCrossingClass = Lots
##  home_team_chanceCreationShootingClass = Normal
##  stage = 2013/2014
##  ->  class TRUE  [0.917]
## 
## Rule 0/7: (8, lift 2.0)
##  away_team_away_wins_p <= 0.2831858
##  away_team_wins > 36
##  home_team_chanceCreationCrossingClass = Lots
##  home_team_chanceCreationShootingClass = Normal
##  stage = 2013/2014
##  ->  class TRUE  [0.900]
## 
## Rule 0/8: (38/8, lift 1.7)
##  away_team_away_wins_p <= 0.3464052
##  home_team_away_wins_p <= 0.368421
##  home_team_chanceCreationPassingClass in {Risky, Safe}
##  home_team_wins > 41
##  stage = 2008/2009
##  ->  class TRUE  [0.775]
## 
## Rule 0/9: (33/8, lift 1.6)
##  away_team_away_wins_p <= 0.3464052
##  away_team_buildUpPlayPositioningClass = Free Form
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.743]
## 
## Rule 0/10: (74/19, lift 1.6)
##  away_team_away_wins_p <= 0.5367647
##  home_team_away_wins_p > 0.368421
##  home_team_buildUpPlaySpeedClass = Fast
##  stage = 2013/2014
##  ->  class TRUE  [0.737]
## 
## Rule 0/11: (372/101, lift 1.6)
##  away_team_away_wins_p <= 0.5367647
##  home_team_away_wins_p > 0.368421
##  home_team_chanceCreationPassingClass = Normal
##  stage = 2009/2010
##  ->  class TRUE  [0.727]
## 
## Rule 0/12: (58/17, lift 1.5)
##  away_team_away_wins_p <= 0.2315789
##  away_team_buildUpPlaySpeedClass = Slow
##  ->  class TRUE  [0.700]
## 
## Rule 0/13: (492/149, lift 1.5)
##  away_team_away_wins_p <= 0.2315789
##  away_team_buildUpPlaySpeedClass = Balanced
##  home_team_chanceCreationPositioningClass = Free Form
##  home_team_chanceCreationShootingClass = Normal
##  ->  class TRUE  [0.696]
## 
## Rule 0/14: (861/268, lift 1.5)
##  away_team_away_wins_p <= 0.5367647
##  home_team_away_wins_p > 0.368421
##  stage in {2010/2011, 2011/2012}
##  ->  class TRUE  [0.688]
## 
## Rule 0/15: (3081/998, lift 1.5)
##  away_team_away_wins_p <= 0.2315789
##  home_team_wins > 33
##  ->  class TRUE  [0.676]
## 
## Rule 0/16: (1653/546, lift 1.5)
##  away_team_away_wins_p <= 0.3464052
##  home_team_wins > 41
##  stage in {2010/2011, 2013/2014, 2014/2015}
##  ->  class TRUE  [0.669]
## 
## Rule 0/17: (3600/1192, lift 1.5)
##  home_team_away_wins_p > 0.368421
##  ->  class TRUE  [0.669]
## 
## Rule 0/18: (402/133, lift 1.5)
##  away_team_api_id <= 35
##  away_team_away_wins_p <= 0.2315789
##  away_team_chanceCreationCrossingClass = Normal
##  home_team_chanceCreationShootingClass = Lots
##  ->  class TRUE  [0.668]
## 
## Rule 0/19: (501/169, lift 1.4)
##  away_team_away_wins_p <= 0.2315789
##  away_team_buildUpPlayPassingClass = Mixed
##  away_team_buildUpPlaySpeedClass = Balanced
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.662]
## 
## Rule 0/20: (1619/582, lift 1.4)
##  away_team_away_wins_p <= 0.1555556
##  ->  class TRUE  [0.640]
## 
## Rule 0/21: (336/122, lift 1.4)
##  away_team_away_wins_p > 0.1555556
##  away_team_away_wins_p <= 0.2315789
##  away_team_buildUpPlaySpeedClass = Balanced
##  home_team_chanceCreationPositioningClass = Organised
##  home_team_chanceCreationShootingClass = Normal
##  home_team_matches > 180
##  stage = 2008/2009
##  ->  class TRUE  [0.636]
## 
## Rule 0/22: (201/77, lift 1.3)
##  away_team_away_wins_p <= 0.2315789
##  away_team_defenceDefenderLineClass = Cover
##  away_team_matches > 218
##  home_team_matches > 180
##  stage = 2015/2016
##  ->  class TRUE  [0.616]
## 
## Rule 0/23: (284/109, lift 1.3)
##  away_team_away_wins_p <= 0.3464052
##  home_team_chanceCreationShootingClass = Little
##  stage in {2009/2010, 2012/2013}
##  ->  class TRUE  [0.615]
## 
## Rule 0/24: (97/38, lift 1.3)
##  away_team_away_wins_p <= 0.2315789
##  away_team_buildUpPlaySpeedClass = Fast
##  away_team_defenceAggressionClass = Press
##  home_team_chanceCreationPassingClass = Risky
##  ->  class TRUE  [0.606]
## 
## Default class: FALSE
## 
## -----  Trial 1:  -----
## 
## Rules:
## 
## Rule 1/1: (893.2/189.3, lift 1.5)
##  away_team_away_wins_p > 0.5367647
##  ->  class FALSE  [0.787]
## 
## Rule 1/2: (45.6/10, lift 1.5)
##  away_team_buildUpPlayDribblingClass in {Little, Normal}
##  away_team_chanceCreationCrossingClass in {Little, Lots}
##  home_team_away_wins_p <= 0.3358209
##  home_team_chanceCreationCrossingClass = Normal
##  home_team_matches > 210
##  stage = 2014/2015
##  ->  class FALSE  [0.769]
## 
## Rule 1/3: (106.9/27.2, lift 1.4)
##  away_team_buildUpPlaySpeedClass = Fast
##  home_team_away_wins_p <= 0.3358209
##  home_team_buildUpPlayPassingClass = Mixed
##  home_team_chanceCreationCrossingClass = Normal
##  stage = 2012/2013
##  ->  class FALSE  [0.741]
## 
## Rule 1/4: (1069.3/282.7, lift 1.4)
##  away_team_matches > 238
##  home_team_wins <= 9
##  ->  class FALSE  [0.735]
## 
## Rule 1/5: (2857/831.1, lift 1.4)
##  away_team_away_wins_p > 0.3636364
##  home_team_away_wins_p <= 0.3486842
##  ->  class FALSE  [0.709]
## 
## Rule 1/6: (152.5/50.2, lift 1.3)
##  home_team_away_wins_p <= 0.3358209
##  home_team_buildUpPlayDribblingClass = Little
##  home_team_chanceCreationCrossingClass = Normal
##  stage = 2011/2012
##  ->  class FALSE  [0.668]
## 
## Rule 1/7: (1666.6/559.4, lift 1.3)
##  away_team_buildUpPlayDribblingClass in {Lots, Normal}
##  home_team_wins <= 9
##  ->  class FALSE  [0.664]
## 
## Rule 1/8: (787.6/300.3, lift 1.2)
##  home_team_away_wins_p <= 0.3358209
##  home_team_buildUpPlayPassingClass in {Long, Short}
##  ->  class FALSE  [0.618]
## 
## Rule 1/9: (473.8/182.1, lift 1.2)
##  away_team_buildUpPlayDribblingClass = Lots
##  home_team_away_wins_p <= 0.3358209
##  ->  class FALSE  [0.615]
## 
## Rule 1/10: (5015.5/1957.7, lift 1.2)
##  home_team_away_wins_p <= 0.3358209
##  home_team_buildUpPlaySpeedClass in {Balanced, Fast}
##  home_team_matches <= 210
##  ->  class FALSE  [0.610]
## 
## Rule 1/11: (1239.5/508.1, lift 1.1)
##  away_team_away_wins_p > 0.1666667
##  away_team_buildUpPlayPassingClass in {Long, Mixed}
##  home_team_away_wins_p <= 0.3358209
##  stage = 2009/2010
##  ->  class FALSE  [0.590]
## 
## Rule 1/12: (2736.6/1144.7, lift 1.1)
##  home_team_away_wins_p <= 0.3358209
##  home_team_chanceCreationCrossingClass = Normal
##  stage in {2013/2014, 2015/2016}
##  ->  class FALSE  [0.582]
## 
## Rule 1/13: (1054.5/445.8, lift 1.1)
##  away_team_buildUpPlayDribblingClass in {Little, Normal}
##  away_team_chanceCreationPositioningClass = Organised
##  home_team_away_wins_p <= 0.3358209
##  home_team_chanceCreationCrossingClass = Normal
##  home_team_chanceCreationPassingClass in {Normal, Safe}
##  stage = 2008/2009
##  ->  class FALSE  [0.577]
## 
## Rule 1/14: (114.6/41.3, lift 1.3)
##  away_team_buildUpPlayDribblingClass = Little
##  away_team_matches <= 238
##  home_team_buildUpPlaySpeedClass = Balanced
##  home_team_wins <= 9
##  ->  class TRUE  [0.637]
## 
## Rule 1/15: (3838.7/1465.5, lift 1.3)
##  away_team_away_wins_p <= 0.5367647
##  home_team_away_wins_p > 0.3486842
##  ->  class TRUE  [0.618]
## 
## Rule 1/16: (13485.4/6482.5, lift 1.1)
##  away_team_away_wins_p <= 0.3636364
##  ->  class TRUE  [0.519]
## 
## Default class: FALSE
## 
## -----  Trial 2:  -----
## 
## Rules:
## 
## Rule 2/1: (972.6/281.3, lift 1.4)
##  away_team_wins > 29
##  home_team_wins <= 9
##  ->  class FALSE  [0.710]
## 
## Rule 2/2: (232.2/80.3, lift 1.3)
##  home_team_buildUpPlaySpeedClass in {Fast, Slow}
##  home_team_wins <= 9
##  ->  class FALSE  [0.653]
## 
## Rule 2/3: (6898.1/2758.3, lift 1.2)
##  away_team_away_wins_p > 0.2720588
##  home_team_away_wins_p <= 0.493421
##  ->  class FALSE  [0.600]
## 
## Rule 2/4: (511.3/210.8, lift 1.1)
##  home_team_away_wins_p <= 0.4055944
##  home_team_buildUpPlayDribblingClass = Normal
##  home_team_chanceCreationShootingClass = Lots
##  ->  class FALSE  [0.587]
## 
## Rule 2/5: (485.6/208.6, lift 1.1)
##  away_team_chanceCreationPassingClass = Safe
##  home_team_away_wins_p <= 0.493421
##  ->  class FALSE  [0.570]
## 
## Rule 2/6: (5071.7/2198.9, lift 1.1)
##  away_team_away_wins_p > 0.1832061
##  home_team_away_wins_p <= 0.4055944
##  home_team_buildUpPlayDribblingClass = Normal
##  stage in {2010/2011, 2011/2012, 2012/2013, 2013/2014}
##  ->  class FALSE  [0.566]
## 
## Rule 2/7: (1248.2/402.8, lift 1.4)
##  home_team_away_wins_p > 0.493421
##  ->  class TRUE  [0.677]
## 
## Rule 2/8: (1127.5/469.9, lift 1.2)
##  away_team_away_wins_p <= 0.3464052
##  home_team_chanceCreationCrossingClass in {Little, Normal}
##  home_team_chanceCreationShootingClass = Normal
##  home_team_wins > 9
##  stage = 2009/2010
##  ->  class TRUE  [0.583]
## 
## Rule 2/9: (3252/1487.5, lift 1.1)
##  away_team_away_wins_p <= 0.3464052
##  home_team_buildUpPlayDribblingClass = Normal
##  home_team_chanceCreationShootingClass = Normal
##  home_team_wins > 9
##  stage in {2008/2009, 2014/2015, 2015/2016}
##  ->  class TRUE  [0.543]
## 
## Rule 2/10: (13007.1/6222.2, lift 1.1)
##  away_team_away_wins_p <= 0.3464052
##  ->  class TRUE  [0.522]
## 
## Default class: FALSE
## 
## -----  Trial 3:  -----
## 
## Rules:
## 
## Rule 3/1: (1435.7/431.6, lift 1.4)
##  away_team_away_wins_p > 0.4666667
##  ->  class FALSE  [0.699]
## 
## Rule 3/2: (533.6/219.6, lift 1.1)
##  home_team_buildUpPlayPassingClass = Long
##  ->  class FALSE  [0.588]
## 
## Rule 3/3: (16231/7687.2, lift 1.0)
##  home_team_wins <= 75
##  ->  class FALSE  [0.526]
## 
## Rule 3/4: (34/9.1, lift 1.5)
##  away_team_defenceAggressionClass = Contain
##  home_team_matches > 150
##  stage = 2015/2016
##  ->  class TRUE  [0.720]
## 
## Rule 3/5: (799.1/244.1, lift 1.4)
##  away_team_away_wins_p <= 0.4666667
##  home_team_wins > 75
##  ->  class TRUE  [0.694]
## 
## Rule 3/6: (1754.1/663.3, lift 1.3)
##  away_team_wins <= 41
##  home_team_away_wins_p > 0.4144737
##  ->  class TRUE  [0.622]
## 
## Rule 3/7: (240.2/97.8, lift 1.2)
##  away_team_buildUpPlaySpeedClass = Slow
##  away_team_wins <= 41
##  home_team_matches > 150
##  ->  class TRUE  [0.592]
## 
## Rule 3/8: (1171.5/486, lift 1.2)
##  away_team_wins <= 41
##  home_team_matches > 150
##  stage = 2013/2014
##  ->  class TRUE  [0.585]
## 
## Rule 3/9: (1122.4/513.1, lift 1.1)
##  away_team_buildUpPlaySpeedClass = Balanced
##  away_team_wins <= 41
##  home_team_buildUpPlaySpeedClass in {Balanced, Fast}
##  home_team_matches > 150
##  stage = 2009/2010
##  ->  class TRUE  [0.543]
## 
## Rule 3/10: (1058.6/490.3, lift 1.1)
##  away_team_chanceCreationCrossingClass = Normal
##  away_team_wins <= 41
##  home_team_buildUpPlayPassingClass = Mixed
##  home_team_matches > 150
##  stage = 2010/2011
##  ->  class TRUE  [0.537]
## 
## Rule 3/11: (3380.6/1588.1, lift 1.1)
##  away_team_buildUpPlaySpeedClass = Balanced
##  away_team_wins <= 41
##  home_team_matches > 150
##  stage in {2008/2009, 2011/2012, 2012/2013}
##  ->  class TRUE  [0.530]
## 
## Default class: FALSE
## 
## -----  Trial 4:  -----
## 
## Rules:
## 
## Rule 4/1: (1405.6/458.8, lift 1.3)
##  away_team_away_wins_p > 0.4666667
##  ->  class FALSE  [0.673]
## 
## Rule 4/2: (213.5/90.3, lift 1.1)
##  away_team_defenceAggressionClass = Double
##  ->  class FALSE  [0.576]
## 
## Rule 4/3: (1474.3/657.4, lift 1.1)
##  away_team_buildUpPlayPassingClass in {Mixed, Short}
##  away_team_defenceAggressionClass = Press
##  home_team_wins <= 46
##  stage = 2009/2010
##  ->  class FALSE  [0.554]
## 
## Rule 4/4: (11430.6/5287.7, lift 1.0)
##  home_team_chanceCreationCrossingClass = Normal
##  home_team_wins <= 46
##  ->  class FALSE  [0.537]
## 
## Rule 4/5: (72.5/23.5, lift 1.4)
##  away_team_buildUpPlayPassingClass = Long
##  away_team_defenceAggressionClass = Press
##  stage = 2009/2010
##  ->  class TRUE  [0.671]
## 
## Rule 4/6: (159.7/64.1, lift 1.2)
##  away_team_away_wins_p <= 0.4666667
##  away_team_chanceCreationShootingClass = Lots
##  stage = 2008/2009
##  ->  class TRUE  [0.597]
## 
## Rule 4/7: (1728.8/807, lift 1.1)
##  away_team_wins <= 59
##  stage = 2013/2014
##  ->  class TRUE  [0.533]
## 
## Rule 4/8: (1682.9/798.7, lift 1.1)
##  away_team_away_wins_p <= 0.4666667
##  away_team_buildUpPlaySpeedClass = Balanced
##  home_team_away_wins_p > 0.1666667
##  stage = 2015/2016
##  ->  class TRUE  [0.525]
## 
## Rule 4/9: (15680.4/7824, lift 1.0)
##  away_team_away_wins_p <= 0.4666667
##  ->  class TRUE  [0.501]
## 
## Default class: FALSE
## 
## -----  Trial 5:  -----
## 
## Rules:
## 
## Rule 5/1: (16574.5/8007.9, lift 1.0)
##  home_team_away_wins_p <= 0.5735294
##  ->  class FALSE  [0.517]
## 
## Rule 5/2: (511.5/158.6, lift 1.4)
##  home_team_away_wins_p > 0.5735294
##  ->  class TRUE  [0.689]
## 
## Rule 5/3: (90/32.2, lift 1.3)
##  away_team_buildUpPlayDribblingClass = Little
##  away_team_chanceCreationCrossingClass = Normal
##  away_team_wins > 10
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.640]
## 
## Rule 5/4: (630/271, lift 1.2)
##  away_team_defenceDefenderLineClass = Cover
##  away_team_wins <= 26
##  home_team_matches <= 111
##  ->  class TRUE  [0.570]
## 
## Rule 5/5: (2250.9/1017.4, lift 1.1)
##  away_team_wins <= 10
##  ->  class TRUE  [0.548]
## 
## Rule 5/6: (1180/543.2, lift 1.1)
##  away_team_away_wins_p <= 0.5073529
##  away_team_buildUpPlayDribblingClass = Normal
##  away_team_buildUpPlayPositioningClass = Organised
##  away_team_chanceCreationCrossingClass = Normal
##  away_team_wins <= 69
##  home_team_chanceCreationShootingClass = Normal
##  home_team_matches > 111
##  stage = 2014/2015
##  ->  class TRUE  [0.540]
## 
## Rule 5/7: (1371.3/637.6, lift 1.1)
##  away_team_away_wins_p <= 0.5073529
##  away_team_wins <= 69
##  home_team_chanceCreationCrossingClass in {Little, Normal}
##  home_team_chanceCreationShootingClass = Normal
##  home_team_matches > 111
##  stage = 2009/2010
##  ->  class TRUE  [0.535]
## 
## Rule 5/8: (2918.8/1403, lift 1.1)
##  away_team_away_wins_p <= 0.5073529
##  away_team_wins <= 69
##  home_team_chanceCreationShootingClass = Normal
##  home_team_matches > 111
##  stage in {2008/2009, 2011/2012}
##  ->  class TRUE  [0.519]
## 
## Default class: FALSE
## 
## -----  Trial 6:  -----
## 
## Rules:
## 
## Rule 6/1: (16266.4/7869.7, lift 1.0)
##  home_team_wins <= 75
##  ->  class FALSE  [0.516]
## 
## Rule 6/2: (819.6/298.5, lift 1.3)
##  home_team_wins > 75
##  ->  class TRUE  [0.635]
## 
## Rule 6/3: (516.9/228.2, lift 1.1)
##  away_team_matches <= 210
##  home_team_matches <= 111
##  ->  class TRUE  [0.558]
## 
## Rule 6/4: (902.9/413.8, lift 1.1)
##  away_team_away_wins_p <= 0.4078947
##  away_team_buildUpPlayDribblingClass = Normal
##  away_team_buildUpPlaySpeedClass in {Fast, Slow}
##  home_team_matches > 111
##  ->  class TRUE  [0.542]
## 
## Rule 6/5: (3251.7/1532.3, lift 1.1)
##  away_team_away_wins_p <= 0.4078947
##  away_team_buildUpPlayDribblingClass = Normal
##  away_team_chanceCreationCrossingClass = Normal
##  home_team_buildUpPlayPassingClass = Mixed
##  home_team_chanceCreationShootingClass = Normal
##  home_team_matches > 111
##  stage in {2010/2011, 2013/2014, 2014/2015}
##  ->  class TRUE  [0.529]
## 
## Rule 6/6: (1480.3/697.9, lift 1.1)
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.528]
## 
## Default class: FALSE
## 
## -----  Trial 7:  -----
## 
## Rules:
## 
## Rule 7/1: (1423.6/536.4, lift 1.2)
##  away_team_away_wins_p > 0.4528302
##  ->  class FALSE  [0.623]
## 
## Rule 7/2: (1538.8/675.2, lift 1.1)
##  home_team_matches <= 111
##  ->  class FALSE  [0.561]
## 
## Rule 7/3: (14262/6966, lift 1.0)
##  away_team_away_wins_p <= 0.4528302
##  home_team_matches > 111
##  ->  class TRUE  [0.512]
## 
## Default class: FALSE
## 
## -----  Trial 8:  -----
## 
## Rules:
## 
## Rule 8/1: (16593.8/8001.3, lift 1.0)
##  home_team_away_wins_p <= 0.5735294
##  ->  class FALSE  [0.518]
## 
## Rule 8/2: (492.2/170.2, lift 1.3)
##  home_team_away_wins_p > 0.5735294
##  ->  class TRUE  [0.654]
## 
## Rule 8/3: (490.6/229.6, lift 1.1)
##  away_team_defenceAggressionClass = Contain
##  ->  class TRUE  [0.532]
## 
## Rule 8/4: (5007.2/2354.4, lift 1.1)
##  away_team_away_wins_p <= 0.5073529
##  away_team_defenceAggressionClass = Press
##  home_team_away_wins_p > 0.3145161
##  ->  class TRUE  [0.530]
## 
## Default class: FALSE
## 
## -----  Trial 9:  -----
## 
## Rules:
## 
## Rule 9/1: (843.4/297.3, lift 1.3)
##  away_team_away_wins_p > 0.5073529
##  home_team_away_wins_p <= 0.5735294
##  ->  class FALSE  [0.647]
## 
## Rule 9/2: (602.3/215.1, lift 1.3)
##  away_team_away_wins_p > 0.2735849
##  home_team_matches <= 110
##  ->  class FALSE  [0.642]
## 
## Rule 9/3: (6239/2835.1, lift 1.1)
##  away_team_away_wins_p > 0.2735849
##  home_team_away_wins_p <= 0.5735294
##  home_team_chanceCreationCrossingClass = Normal
##  ->  class FALSE  [0.546]
## 
## Rule 9/4: (487.1/174.9, lift 1.3)
##  home_team_away_wins_p > 0.5735294
##  ->  class TRUE  [0.640]
## 
## Rule 9/5: (123.8/47.3, lift 1.3)
##  away_team_away_wins_p > 0.2735849
##  away_team_chanceCreationPassingClass = Risky
##  stage = 2008/2009
##  ->  class TRUE  [0.616]
## 
## Rule 9/6: (16199.3/8099, lift 1.0)
##  away_team_away_wins_p <= 0.5073529
##  ->  class TRUE  [0.500]
## 
## Default class: FALSE
## 
## -----  Trial 10:  -----
## 
## Rules:
## 
## Rule 10/1: (16603.8/8036.2, lift 1.0)
##  home_team_away_wins_p <= 0.5735294
##  ->  class FALSE  [0.516]
## 
## Rule 10/2: (482.2/179, lift 1.3)
##  home_team_away_wins_p > 0.5735294
##  ->  class TRUE  [0.628]
## 
## Default class: FALSE
## 
## -----  Trial 11:  -----
## 
## Rules:
## 
## Rule 11/1: (644.1/235.5, lift 1.3)
##  away_team_wins > 76
##  ->  class FALSE  [0.634]
## 
## Rule 11/2: (10575.3/5122.2, lift 1.0)
##  away_team_chanceCreationCrossingClass in {Little, Normal}
##  away_team_wins > 23
##  home_team_away_wins_p <= 0.6447368
##  ->  class FALSE  [0.516]
## 
## Rule 11/3: (15893.4/7803.5, lift 1.0)
##  home_team_wins <= 69
##  ->  class FALSE  [0.509]
## 
## Rule 11/4: (45.8/14.7, lift 1.4)
##  away_team_buildUpPlayPositioningClass = Free Form
##  home_team_buildUpPlayDribblingClass = Normal
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.673]
## 
## Rule 11/5: (151.1/58.5, lift 1.2)
##  home_team_buildUpPlayPassingClass = Short
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.611]
## 
## Rule 11/6: (114.2/46, lift 1.2)
##  home_team_buildUpPlayPassingClass = Long
##  home_team_chanceCreationShootingClass = Little
##  ->  class TRUE  [0.596]
## 
## Rule 11/7: (1428.2/663, lift 1.1)
##  away_team_api_id > 4
##  away_team_chanceCreationShootingClass = Normal
##  away_team_wins <= 76
##  home_team_buildUpPlayPassingClass = Mixed
##  stage = 2009/2010
##  ->  class TRUE  [0.536]
## 
## Rule 11/8: (12377.1/6068.3, lift 1.0)
##  away_team_api_id > 4
##  away_team_wins <= 76
##  home_team_buildUpPlayPassingClass = Mixed
##  home_team_chanceCreationShootingClass in {Lots, Normal}
##  ->  class TRUE  [0.510]
## 
## Rule 11/9: (16441.9/8182.1, lift 1.0)
##  away_team_wins <= 76
##  ->  class TRUE  [0.502]
## 
## Default class: FALSE
## 
## -----  Trial 12:  -----
## 
## Rules:
## 
## Rule 12/1: (638.2/239.9, lift 1.2)
##  away_team_wins > 76
##  ->  class FALSE  [0.624]
## 
## Rule 12/2: (1665.9/789.1, lift 1.1)
##  away_team_chanceCreationCrossingClass in {Little, Lots}
##  home_team_wins <= 69
##  ->  class FALSE  [0.526]
## 
## Rule 12/3: (16447.8/8145.3, lift 1.0)
##  away_team_wins <= 76
##  ->  class TRUE  [0.505]
## 
## Default class: FALSE
## 
## -----  Trial 13:  -----
## 
## Rules:
## 
## Rule 13/1: (95.1/29.7, lift 1.4)
##  away_team_buildUpPlayDribblingClass in {Little, Lots}
##  home_team_buildUpPlayPassingClass = Mixed
##  home_team_chanceCreationPassingClass = Safe
##  ->  class FALSE  [0.684]
## 
## Rule 13/2: (862.7/335.2, lift 1.2)
##  away_team_away_wins_p > 0.5073529
##  ->  class FALSE  [0.611]
## 
## Rule 13/3: (526.7/243, lift 1.1)
##  home_team_buildUpPlayPassingClass = Long
##  ->  class FALSE  [0.538]
## 
## Rule 13/4: (13035.8/6369.5, lift 1.0)
##  away_team_buildUpPlayPassingClass = Mixed
##  home_team_away_wins_p <= 0.5241935
##  home_team_chanceCreationPassingClass = Normal
##  ->  class FALSE  [0.511]
## 
## Rule 13/5: (294.8/131.8, lift 1.1)
##  away_team_buildUpPlaySpeedClass = Slow
##  home_team_buildUpPlayDribblingClass = Normal
##  ->  class TRUE  [0.552]
## 
## Rule 13/6: (16223.3/8102.8, lift 1.0)
##  away_team_away_wins_p <= 0.5073529
##  ->  class TRUE  [0.501]
## 
## Default class: FALSE
## 
## -----  Trial 14:  -----
## 
## Rules:
## 
## Rule 14/1: (138.4/45.6, lift 1.3)
##  home_team_away_wins_p <= 0.1666667
##  stage = 2015/2016
##  ->  class FALSE  [0.668]
## 
## Rule 14/2: (558.2/212.4, lift 1.2)
##  away_team_wins > 77
##  ->  class FALSE  [0.619]
## 
## Rule 14/3: (411.2/179.3, lift 1.1)
##  home_team_buildUpPlayPassingClass = Long
##  home_team_chanceCreationShootingClass in {Lots, Normal}
##  ->  class FALSE  [0.564]
## 
## Rule 14/4: (16527.8/8195.7, lift 1.0)
##  away_team_wins <= 77
##  ->  class TRUE  [0.504]
## 
## Default class: TRUE
## 
## -----  Trial 15:  -----
## 
## Rules:
## 
## Rule 15/1: (16776/8250.9, lift 1.0)
##  home_team_away_wins_p <= 0.6447368
##  ->  class FALSE  [0.508]
## 
## Rule 15/2: (310/116.1, lift 1.3)
##  home_team_away_wins_p > 0.6447368
##  ->  class TRUE  [0.625]
## 
## Default class: FALSE
## 
## -----  Trial 16:  -----
## 
## Rules:
## 
## Rule 16/1: (44.4/9.8, lift 1.5)
##  away_team_api_id > 32
##  home_team_chanceCreationPassingClass = Safe
##  ->  class FALSE  [0.768]
## 
## Rule 16/2: (107.1/39.6, lift 1.3)
##  away_team_buildUpPlayDribblingClass in {Little, Lots}
##  home_team_chanceCreationPassingClass = Safe
##  ->  class FALSE  [0.628]
## 
## Rule 16/3: (853.7/341.3, lift 1.2)
##  away_team_away_wins_p > 0.5073529
##  ->  class FALSE  [0.600]
## 
## Rule 16/4: (16232.3/8043.6, lift 1.0)
##  away_team_away_wins_p <= 0.5073529
##  ->  class TRUE  [0.504]
## 
## Default class: FALSE
## 
## -----  Trial 17:  -----
## 
## Rules:
## 
## Rule 17/1: (16620.2/8159, lift 1.0)
##  home_team_away_wins_p <= 0.5735294
##  ->  class FALSE  [0.509]
## 
## Rule 17/2: (465.8/188.4, lift 1.2)
##  home_team_away_wins_p > 0.5735294
##  ->  class TRUE  [0.595]
## 
## Default class: FALSE
## 
## -----  Trial 18:  -----
## 
## Rules:
## 
## Rule 18/1: (1285.4/547.5, lift 1.1)
##  away_team_away_wins_p > 0.4666667
##  ->  class FALSE  [0.574]
## 
## Rule 18/2: (15908.1/7863.2, lift 1.0)
##  home_team_wins <= 69
##  ->  class FALSE  [0.506]
## 
## Rule 18/3: (1090.8/469.4, lift 1.1)
##  away_team_away_wins_p <= 0.4666667
##  home_team_wins > 69
##  ->  class TRUE  [0.570]
## 
## Default class: FALSE
## 
## *** boosting reduced to 18 trials since last classifier is very inaccurate
## 
## 
## Evaluation on training data (17086 cases):
## 
## Trial            Rules     
## -----      ----------------
##      No      Errors
## 
##    0     24 5914(34.6%)
##    1     16 6281(36.8%)
##    2     10 6597(38.6%)
##    3     11 6481(37.9%)
##    4      9 6854(40.1%)
##    5      8 7151(41.9%)
##    6      6 7258(42.5%)
##    7      3 7687(45.0%)
##    8      4 6466(37.8%)
##    9      6 6916(40.5%)
##   10      2 7471(43.7%)
##   11      9 6981(40.9%)
##   12      3 8438(49.4%)
##   13      6 7197(42.1%)
##   14      4 8456(49.5%)
##   15      2 7589(44.4%)
##   16      4 8457(49.5%)
##   17      2 7471(43.7%)
## boost           5903(34.5%)   <<
## 
## 
##     (a)   (b)    <-classified as
##    ----  ----
##    6950  2276    (a): class FALSE
##    3627  4233    (b): class TRUE
## 
## 
##  Attribute usage:
## 
##  100.00% away_team_away_wins_p
##  100.00% away_team_wins
##  100.00% home_team_away_wins_p
##  100.00% home_team_wins
##   93.56% home_team_matches
##   90.97% home_team_chanceCreationShootingClass
##   90.74% stage
##   80.81% home_team_buildUpPlayPassingClass
##   78.94% away_team_chanceCreationCrossingClass
##   78.15% home_team_chanceCreationPassingClass
##   77.88% away_team_buildUpPlayPassingClass
##   76.64% home_team_chanceCreationCrossingClass
##   72.75% away_team_api_id
##   51.73% home_team_buildUpPlayDribblingClass
##   44.38% away_team_buildUpPlaySpeedClass
##   42.28% away_team_defenceAggressionClass
##   42.05% away_team_buildUpPlayDribblingClass
##   35.81% home_team_buildUpPlaySpeedClass
##   10.69% away_team_matches
##    9.21% away_team_chanceCreationShootingClass
##    6.94% away_team_buildUpPlayPositioningClass
##    6.12% away_team_chanceCreationPositioningClass
##    4.85% home_team_chanceCreationPositioningClass
##    4.62% away_team_defenceDefenderLineClass
##    3.54% away_team_chanceCreationPassingClass
## 
## 
## Time: 2.3 secs

Seguim obtenint moltes regles, per tant, el que hem de fer ara és podar l’arbre. La llibreria C50 ens permet especificar si volem fer servir mètodes de poda, com per exemple winnow, que fa una selecció dels atributs i noGlobalPruning, que simplifica l’arbre:

c50_model_prune <- C5.0(train_imp_X, train_y, rules = TRUE,
                        control = C5.0Control(winnow = TRUE))
summary(c50_model_prune)
## 
## Call:
## C5.0.default(x = train_imp_X, y = train_y, rules = TRUE, control
##  = C5.0Control(winnow = TRUE))
## 
## 
## C5.0 [Release 2.07 GPL Edition]      Tue Jan 24 21:44:40 2023
## -------------------------------
## 
## Class specified by attribute `outcome'
## 
## Read 17086 cases (27 attributes) from undefined.data
## 
## 16 attributes winnowed
## Estimated importance of remaining attributes:
## 
##       6%  away_team_away_wins_p
##       2%  home_team_away_wins_p
##       2%  home_team_wins
##      <1%  home_team_buildUpPlaySpeedClass
##      <1%  away_team_buildUpPlaySpeedClass
##      <1%  home_team_buildUpPlayPassingClass
##      <1%  away_team_chanceCreationCrossingClass
##      <1%  home_team_buildUpPlayDribblingClass
##      <1%  away_team_buildUpPlayPositioningClass
##      <1%  away_team_chanceCreationPositioningClass
## 
## Rules:
## 
## Rule 1: (953/156, lift 1.5)
##  away_team_away_wins_p > 0.5367647
##  ->  class FALSE  [0.836]
## 
## Rule 2: (13/2, lift 1.5)
##  home_team_away_wins_p > 0.368421
##  home_team_buildUpPlayPassingClass = Long
##  ->  class FALSE  [0.800]
## 
## Rule 3: (8436/2837, lift 1.2)
##  away_team_away_wins_p > 0.2315789
##  home_team_away_wins_p <= 0.368421
##  ->  class FALSE  [0.664]
## 
## Rule 4: (8267/2905, lift 1.2)
##  away_team_away_wins_p > 0.1555556
##  home_team_away_wins_p <= 0.368421
##  home_team_wins <= 33
##  ->  class FALSE  [0.649]
## 
## Rule 5: (3455/1084, lift 1.5)
##  away_team_away_wins_p <= 0.5367647
##  home_team_away_wins_p > 0.368421
##  home_team_buildUpPlayPassingClass in {Mixed, Short}
##  ->  class TRUE  [0.686]
## 
## Rule 6: (3081/998, lift 1.5)
##  away_team_away_wins_p <= 0.2315789
##  home_team_wins > 33
##  ->  class TRUE  [0.676]
## 
## Rule 7: (1619/582, lift 1.4)
##  away_team_away_wins_p <= 0.1555556
##  ->  class TRUE  [0.640]
## 
## Default class: FALSE
## 
## 
## Evaluation on training data (17086 cases):
## 
##          Rules     
##    ----------------
##      No      Errors
## 
##       7 6144(36.0%)   <<
## 
## 
##     (a)   (b)    <-classified as
##    ----  ----
##    7161  2065    (a): class FALSE
##    4079  3781    (b): class TRUE
## 
## 
##  Attribute usage:
## 
##   99.93% away_team_away_wins_p
##   85.23% home_team_away_wins_p
##   66.42% home_team_wins
##   20.30% home_team_buildUpPlayPassingClass
## 
## 
## Time: 0.5 secs

A continuació fem una breu explicació de les regles extretes:

  • Si l’equip visitant té un percentatge de victòries fora de casa superior al 53,7 %, llavors el local no guanyarà.
  • Si l’equip local té un percentatge de victòries fora de casa superior al 36,8 % i juguen a fer passades llargues, llavors el local no guanyarà.
  • Si l’equip visitant té un percentatge de victòries fora de casa superior al 23,2 %, i l’equip local té un percentatge de victòries fora inferior al 36,9 %, llavors el local no guanyarà.
  • Si l’equip visitant té un percentatge de victòries fora de casa superior al 15,6 %, l’equip local té un percentatge de victòries fora inferior al 36,8 %, i les victòries de l’equip local són inferiors a 33, llavors el local no guanyarà.
  • Si l’equip visitant té un percentatge de victòries fora de casa inferior al 53,7 %, l’equip local té un percentatge de victòries fora superior al 36,8 %, i el tipus de passades de l’equip local són curtes o barrejades, llavors el local guanyarà.
  • Si l’equip visitant té un percentatge de victòries fora de casa inferior al 23,2 %, i les victòries de l’equip local són inferiors a 33, llavors el local guanyarà.
  • Si l’equip visitant té un percentatge de victòries fora de casa inferior al 15,6 %, llavors el local guanyarà.

Com podem veure, el model dona molta importància al rediment passat dels equips, de fet, la variable que més usa és la proporció de victòries fora de casa de l’equip visitant.

I ara el visualitzem:

c50_model_prune <- C5.0(train_imp_X, train_y,
                        control = C5.0Control(winnow = TRUE))
plot(c50_model_prune, gp = gpar(fontsize = 9.5))

Com podem comprovar, hem obtingut un arbre molt més senzill i més fàcil d’explicar.

2.4.3 Anàlisi de la qualitat

En total hem creat tres models diferents basats en els arbres C5.0 de Quinlan. Hem pogut veure la seua representació gràfica i les regles en forma escrita. Però encara no sabem quins models funcionen millor. Per a fer-ho, hem d’estudiar la seua qualitat mostrant la matriu de confusió per a cadascun dels arbres. A més, calculem la seua precisió (precision), la sensibilitat (recall) i la f-measure per tenir unes mètriques que ens ajudin a escollir el millor model.

Carreguem el paquet gmodels per a poder fer servir la funció CrossTable(...), que calcula la matriu de confusió:

packages <- c("gmodels")

not_installed <- packages[!(packages %in% installed.packages())]
if (length(not_installed) > 0) {
  install.packages(not_installed, repos = "http:/cran.us.r-project.org")
}
lapply(packages, library, character.only = TRUE)
## [[1]]
##  [1] "gmodels"    "grid"       "C50"        "dbscan"     "fpc"       
##  [6] "cluster"    "corrplot"   "factoextra" "xfun"       "dplyr"     
## [11] "Rmisc"      "plyr"       "lattice"    "ggplot2"    "RSQLite"   
## [16] "stats"      "graphics"   "grDevices"  "utils"      "datasets"  
## [21] "methods"    "base"

A continuació, carreguem les funcions que ens ajudaran a estudiar la qualitat:

calculate_accuracy <- function(predicted, correct) {
  return(sum(predicted == correct) / length(predicted))
}

calculate_precision <- function(predicted, correct) {
  cross_table <- gmodels::CrossTable(correct, predicted,
    prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
    dnn = c("Reality", "Prediction")
  )

  precision <- cross_table$prop.col[2, 2]
  return(precision)
}

calculate_recall <- function(predicted, correct) {
  cross_table <- gmodels::CrossTable(correct, predicted,
    prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE,
    dnn = c("Reality", "Prediction")
  )

  recall <- cross_table$prop.row[2, 2]
  return(recall)
}

calculate_f_measure <- function(precision, recall) {
  f_measure <- (precision * recall) / (precision + recall)
  return(f_measure)
}

Creem un data frame on guardarem els resultats per a cada model:

models_names <- c("Basic", "Importance", "Pruned")
df_tree_quality <- data.frame(model = models_names,
                              accuracy = double(length(models_names)),
                              precision = double(length(models_names)),
                              recall = double(length(models_names)),
                              f_measure = double(length(models_names)))

Ara, procedim a calcular les diferents mètriques per a cada model:

predicted_labels <- predict(c50_model, test_X, type = "class")

df_tree_quality[1, ]$accuracy <- calculate_accuracy(predicted_labels, test_y)
df_tree_quality[1, ]$precision <- calculate_precision(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3453 |      1194 |      4647 | 
##              |     0.404 |     0.140 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      1920 |      1976 |      3896 | 
##              |     0.225 |     0.231 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5373 |      3170 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[1, ]$recall <- calculate_recall(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3453 |      1194 |      4647 | 
##              |     0.404 |     0.140 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      1920 |      1976 |      3896 | 
##              |     0.225 |     0.231 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5373 |      3170 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[1, ]$f_measure <-
  calculate_f_measure(df_tree_quality[1, ]$precision,
                      df_tree_quality[1, ]$recall)
predicted_labels <- predict(c50_model_imp, test_imp_X, type = "class")

df_tree_quality[2, ]$accuracy <- calculate_accuracy(predicted_labels, test_y)
df_tree_quality[2, ]$precision <- calculate_precision(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3455 |      1192 |      4647 | 
##              |     0.404 |     0.140 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      1914 |      1982 |      3896 | 
##              |     0.224 |     0.232 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5369 |      3174 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[2, ]$recall <- calculate_recall(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3455 |      1192 |      4647 | 
##              |     0.404 |     0.140 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      1914 |      1982 |      3896 | 
##              |     0.224 |     0.232 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5369 |      3174 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[2, ]$f_measure <-
  calculate_f_measure(df_tree_quality[2, ]$precision,
                      df_tree_quality[2, ]$recall)
predicted_labels <- predict(c50_model_prune, test_imp_X, type = "class")

df_tree_quality[3, ]$accuracy <- calculate_accuracy(predicted_labels, test_y)
df_tree_quality[3, ]$precision <- calculate_precision(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3594 |      1053 |      4647 | 
##              |     0.421 |     0.123 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      2097 |      1799 |      3896 | 
##              |     0.245 |     0.211 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5691 |      2852 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[3, ]$recall <- calculate_recall(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3594 |      1053 |      4647 | 
##              |     0.421 |     0.123 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      2097 |      1799 |      3896 | 
##              |     0.245 |     0.211 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5691 |      2852 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[3, ]$f_measure <-
  calculate_f_measure(df_tree_quality[3, ]$precision,
                      df_tree_quality[3, ]$recall)
df_tree_quality
##        model  accuracy precision    recall f_measure
## 1      Basic 0.6354910 0.6233438 0.5071869 0.2796490
## 2 Importance 0.6364275 0.6244486 0.5087269 0.2803395
## 3     Pruned 0.6312771 0.6307854 0.4617556 0.2665975

Veiem com els resultats són molt similars entre tots els models. El model que té una f-measure més alta és el que està filtrat amb les variables més importants. També veiem com l’arbre podat té una precisió un pèl més alta que la resta, però amb la pitjor sensibilitat.

Queda clar que el fet de reduir el nombre de regles no afecta negativament a la qualitat del model. Per això, ens quedem amb l’últim, ja que, amb resultats similars (encara que pitjors) és molt més fàcil d’explicar.

2.4.4 Resultats

Els resultats dels models no són els desitjats, però almenys hem aconseguit descriure en poques regles si l’equip local guanyarà o no.

Els motius pels quals no ha acabat de funcionar són que el resultat d’un partit de futbol és molt complicat d’endevinar. Hi intervenen molts factors i ni les persones més expertes són capaces de fer una predicció fiable.

També és possible que aquest tipus de models basats en arbres de decisió no siguin adequats per a resoldre aquest problema.

Els tres arbres generats s’han basat principalment en els resultats històrics dels equips. Això té molt sentit, ja que, si veiem que s’enfronten equips amb una diferència de qualitat molt gran és fàcil deduir que l’equip més bo guanyarà.

La resta d’informació proporcionada als models tenia en compte l’estil de joc dels equips. Pel que sembla, l’estil de joc no és gaire determinant de cara a predir qui guanyarà.

De cara a futurs anàlisis, seria molt interessant afegir informació dels equips. Per exemple, es podria afegir els gols per partit que marquen, o la valoració mitjana dels jugadors de la plantilla. Això no s’ha fet en aquesta versió perquè aquesta informació no era fàcil de trobar. Per afegir-la, cal fer càlculs i creuar dades entre les taules i malauradament, no s’ha disposat del temps necessari per a fer-ho.

2.5 Model supervisat Random Forest

En la secció anterior hem vist com l’arbre de decisió C5.0 de Quinlan no ha donat gaires bons resultats. En aquesta secció aplicarem el model supervisat Random Forest. Aquest es basa a crear molts arbres de decisió diferents i fer-los servir per a prendre decisions en conjunt.

El que volem predir és el mateix que en l’apartat anterior, és a dir, si l’equip local guanyarà el partit. Per a que els models siguin comparables utilitzem les mateixes dades que anteriorment.

Abans de res, carreguem la llibreria randomForest que és la que ens permetrà entrenar el model:

packages <- c("randomForest")

not_installed <- packages[!(packages %in% installed.packages())]
if (length(not_installed) > 0) {
  install.packages(not_installed, repos = "http:/cran.us.r-project.org")
}
lapply(packages, library, character.only = TRUE)
## [[1]]
##  [1] "randomForest" "gmodels"      "grid"         "C50"          "dbscan"      
##  [6] "fpc"          "cluster"      "corrplot"     "factoextra"   "xfun"        
## [11] "dplyr"        "Rmisc"        "plyr"         "lattice"      "ggplot2"     
## [16] "RSQLite"      "stats"        "graphics"     "grDevices"    "utils"       
## [21] "datasets"     "methods"      "base"

2.5.1 Generació del model

Ara que hem definit la tasca i ja hem carregat els paquets necessaris, ja podem generar el model.

Ho fem amb la funció randomForest(...). Aquesta admet el paràmetre ntree que serveix per indicar el nombre d’arbres a crear. Escollim un nombre alt per assegurar-nos que cada fila es prediu diverses vegades:

rf_model <- randomForest(x = train_X, y = train_y, ntree = 5000)
rf_model
## 
## Call:
##  randomForest(x = train_X, y = train_y, ntree = 5000) 
##                Type of random forest: classification
##                      Number of trees: 5000
## No. of variables tried at each split: 5
## 
##         OOB estimate of  error rate: 37.97%
## Confusion matrix:
##       FALSE TRUE class.error
## FALSE  6477 2749   0.2979623
## TRUE   3738 4122   0.4755725

2.5.2 Anàlisi de la qualitat

Ara que ja tenim el model creat, podem avaluar-ne la seua qualitat. Fem servir les mateixes mètriques que abans, per tant, no cal carregar cap funció nova:

predicted_labels <- predict(rf_model, test_X, type = "class")

df_tree_quality[4, ]$model <- "Random Forest"
df_tree_quality[4, ]$accuracy <- calculate_accuracy(predicted_labels, test_y)
df_tree_quality[4, ]$precision <- calculate_precision(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3286 |      1361 |      4647 | 
##              |     0.385 |     0.159 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      1873 |      2023 |      3896 | 
##              |     0.219 |     0.237 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5159 |      3384 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[4, ]$recall <- calculate_recall(predicted_labels, test_y)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  8543 
## 
##  
##              | Prediction 
##      Reality |     FALSE |      TRUE | Row Total | 
## -------------|-----------|-----------|-----------|
##        FALSE |      3286 |      1361 |      4647 | 
##              |     0.385 |     0.159 |           | 
## -------------|-----------|-----------|-----------|
##         TRUE |      1873 |      2023 |      3896 | 
##              |     0.219 |     0.237 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      5159 |      3384 |      8543 | 
## -------------|-----------|-----------|-----------|
## 
## 
df_tree_quality[4, ]$f_measure <-
  calculate_f_measure(df_tree_quality[4, ]$precision,
                      df_tree_quality[4, ]$recall)

df_tree_quality
##            model  accuracy precision    recall f_measure
## 1          Basic 0.6354910 0.6233438 0.5071869 0.2796490
## 2     Importance 0.6364275 0.6244486 0.5087269 0.2803395
## 3         Pruned 0.6312771 0.6307854 0.4617556 0.2665975
## NA Random Forest 0.6214445 0.5978132 0.5192505 0.2778846

Com podem veure, els resultats són molt similars que els obtinguts amb els arbres de decisió C5.0 de Quinlan. Malgrat tot, aquests tenen la sensibilitat una mica més alta que la resta, però, en canvi, perden precisió. Si ens basem en la f-measure podem determinar que és el segon o tercer (depèn de l’execució) millor model dels quatre que hem creat. Això ens deixa en la mateixa situació que en la que estàvem, ja que, no hem assolit un model prou fiable per a predir si el guanyador del partit serà l’equip local.

2.5.3 Resultats

El que podem concloure d’aquestes anàlisis és que probablement tots els models basats en arbres de decisió aconsegueixin uns resultats similars. Estaria bé provar amb un altre tipus d’algorismes per a saber si funcionen millor o pitjor.

En tot cas, el que és més probable que estigui passant és que les dades són insuficients per a intentar predir això. Hauríem de profunditzar en la recollida de dades i calcular les noves característiques que ja s’han comentat en l’apartat anterior.

2.6 Limitacions i riscos del dataset

Després d’analitzar i treballar amb aquest conjunt de dades és important, de cara a nous treballs, destacar els riscos que comporta utilitzar-lo. A continuació, esmentem alguns dels perills que ens podem trobar:

  • Dataset molt gran, és complicat poder analitzar totes les dades, per tant, cal fer una selecció de què es vol. Un dels riscos de fer això és que els fets a estudiar no donin un bon resultat. En canvi, podria ser que si s’haguessin avaluat unes altres taules s’haguessin aconseguit millors resultats.
  • El fet de tenir la informació dividida en diferents taules també és una complicació perquè vol dir que la informació no està disponible directament, sinó que cal creuar les dades entre taules i fer els càlculs manualment per obtenir-la. Això implica haver de dedicar encara més temps a l’anàlisi inicial i obliga a tenir uns bons coneixements del llenguatge de programació utilitzat.
  • Una altra conseqüència que tingui tants registres és que amb un ordinador personal la memòria RAM es pot quedar curta i també realitzar alguns càlculs és molt lent i, per tant, es poden fer menys proves del que es voldrien. Evidentment, el més normal és trobar-nos amb taules amb moltes més entrades, però en aquests casos s’acostuma a treballar al núvol o amb ordinadors personals molt més potents.
  • El dataset semblava complet, però amb l’anàlisi prèvia ja es va veure que no ho era tant. Això fa que es perdin moltes dades útils. De fet, s’havia escollit aquest conjunt de dades pel fet de ser complet. Els altres datasets no tenien tantes mètriques, però és possible que tinguessin menys registres buits.
  • Una altra característica d’aquestes dades que cal tenir en compte és que no està prou actualitzat. Les dades van de l’any 2008 fins a l’any 2016. Això és un període de temps molt curt comparat amb els anys que fa que es juga a aquest esport. A més, ja fa sis anys que no s’actualitzen les dades. Per tant, és important que quan s’utilitzen aquestes dades es tingui clar que no poden ser representatives ni de tot el futbol ni de la història més recent. Només serveixen per a analitzar-lo en aquell període en concret. Un exercici interessant podria consistir a comparar les conclusions extretes fent servir aquestes dades amb altres períodes de la història del futbol.

2.7 Conclusions

Un cop acabat aquest projecte considerem important fer alguns apunts sobre aquest. En aquest apartat es comenten les principals limitacions que hem trobat, una explicació sobre com les hem solucionat i, finalment una valoració sobre el coneixement adquirit durant la realització de l’activitat.

Com ja s’ha comentat en l’apartat anterior, durant la realització del treball s’han trobat algunes dificultats inesperades que han complicat el projecte. A continuació esmentem algunes d’elles:

  • Les dades tenien una certa complexitat, ja que, la documentació era escassa.
  • Les dades estaven distribuïdes en múltiples taules. Això implicava que alguns atributs no estiguessin disponibles de forma fàcil.
  • S’esperava que el conjunt de dades tingués moltes més variables útils. Ja hem vist que n’hi havia moltes d’interessants que estaven buides o mal formatades. Això ha implicat una pèrdua d’informació molt gran.
  • Les dades ocupaven un gran volum en memòria. Per a consultar-les no era un problema, però quan s’intentava treballar amb elles la memòria RAM s’esgotava ràpidament.

Les solucions que s’han trobat per a les anteriors dificultats han sigut:

  • Estudiar a consciència de les dades fins a entendre com estaven organitzades, el significat de cada atribut i el perquè hi havia identificadors repetits.
  • Per evitar la dispersió de les variables similars, s’han unit en la mateixa taula les que tenien un significat semblant.
  • Per poder treballar amb aquestes dades ha calgut ometre les columnes que estaven malament i crear nous atributs a partir dels existents.
  • S’ha optimitzat el volum de dades carregades en memòria en un mateix instant. El que s’ha fet ha sigut esborrar els data frames i variables auxiliars a mesura que deixaven de ser útils. D’aquesta manera s’ha pogut treballar evitant que l’ordinador es pengés.

Malgrat la poca experiència en solucionar aquest tipus de problemes, crec que en general ens n’hem sortit de forma satisfactòria.

Per acabar, volem comentar que durant aquest projecte sobretot hem après que cal estudiar i entendre molt bé les dades abans de començar a tractar-les. També hem vist que si no es planifiquen correctament els punts a estudiar és molt probable que ens deixem coses durant el pretractament de les dades. Si això passa, el fet de tornar enrere i tractar un altre cop les dades acaba comportant una pèrdua de temps important.

Malgrat que ja teníem una certa experiència realitzant projectes de mineria de dades, mai n’havíem dut a terme un de tan complet. Sempre havien sigut més curts i fàcils. Haver fet aquest exercici des del principi fins al final ens ha millorat la capacitat analítica i ens ha fet donar compte que les dades són vitals per aconseguir un bon resultat.

També hem millorat molt amb l’ús del llenguatge R. Fins ara havíem acomplert petits projectes que ens havien donat una idea sobre com fer-lo anar. Però ara notem que anem molt més ràpids a l’hora de programar.

A més, en el conjunt global de l’assignatura, també valorem molt el fet d’haver realitzat tantes pràctiques diferents, ja que, en cada una tocat temes diferents i ens ha ajudat a aprofundir en elles. Gràcies a elles també hem pogut recopilar una sèrie d’snippets que es podran aplicar en altres projectes.

Finalment, voldríem comentar que aquesta assignatura ens ha semblat útil i completa perquè molt probablement podrem aplicar aquests coneixements en altres projectes laborals i personals.


3 Bibliografia


Aquesta secció conté les pàgines web visitades durant el treball i d’on s’ha agafat idees per a l’elaboració del treball.

K-Means

A més, durant la realització del treball també s’ha consultat en algunes ocasions ChatGPT per a orientar-nos a fer alguns càlculs. Sempre s’ha fet servir com a eina de suport i s’ha comprovat que el que deia era veritat. De fet, s’ha pogut demostrar, que en certs casos no és capaç de donar un codi vàlid en R.